Linting là quá trình phân tích mã tĩnh để tìm kiếm các vấn đề tiềm ẩn.
Điều gì tạo nên vấn đề, trong trường hợp này, có thể khác nhau giữa các ngôn ngữ lập trình hoặc giữa các dự án trong cùng một ngôn ngữ. Tôi sẽ đặt các vấn đề này dưới một vài danh mục khác nhau:
- Có lập trình
- Bảo mật
- Phong cách
- Hiệu suất
Hãy xem một vài ví dụ về từng loại.
Vấn đề về phong cách
Không có cách tạo kiểu mã đúng đắn một cách khách quan, vì tất cả là về sở thích của người đó. Điều quan trọng là sự nhất quán. Những điểm chung của cuộc tranh luận bao gồm:
- dấu ngoặc kép so với dấu ngoặc đơn
- tab so với khoảng trắng
- độ dài dòng tối đa
- thụt lề của cuộc gọi nhiều dòng, như được hiển thị bên dưới:
# always single-line
foo(:a, :b, :c)
# aligned with first argument
foo(:a,
:b,
:c
)
# aligned with function name
foo(
:a,
:b
)
Đây là những điều hoàn toàn chủ quan, nhưng thường có lợi nếu đồng ý về một tiêu chuẩn cho từng dự án cụ thể, để giữ cho toàn bộ cơ sở mã nhất quán.
Sự cố lập trình
Tôi đưa vào đây những vấn đề như:
- Các phần thân của phương thức cực kỳ dài, dẫn đến khả năng dễ đọc và khả năng bảo trì
- Độ phức tạp theo chu kỳ, một số liệu thường được sử dụng để đo độ phức tạp của mã
- Các nhiệm vụ trong điều kiện. Rất có thể, nếu bạn nhập
if x = true
, ý bạn thực sự làif x == true
. và ngay cả khi ý bạn là một nhiệm vụ, thì đó vẫn là một cách tiếp cận kém trực quan hơn
Sự cố bảo mật
Một số chức năng hoặc thực tiễn tiềm ẩn các vấn đề bảo mật mà nhà phát triển có thể không biết.
Ví dụ, trong Ruby, Kernel#open
là một chức năng linh hoạt cho phép mở tệp hoặc URL bên ngoài. Nhưng nó cũng cho phép truy cập hệ thống tập tin tùy ý, với các lệnh gọi lạ như open("| ls")
Do đó, nên cảnh báo các nhà phát triển về điều này để họ có thể sử dụng phương pháp tiếp cận an toàn hơn (File#open
, IO.popen
, URI.parse#open
), hoặc quy định rõ ràng để giữ cho hành vi đó tự chịu rủi ro.
Vấn đề về hiệu suất
Có nhiều chi tiết về hoạt động bên trong của Ruby khiến một số tùy chọn hoạt động hiệu quả hơn những tùy chọn khác, tùy thuộc vào ngữ cảnh.
Một người nói dối cảnh báo chúng tôi về chúng sẽ giúp chúng tôi tìm hiểu trong suốt quá trình đồng thời tối ưu hóa một số chi tiết về chương trình của chúng tôi.
Ví dụ:Ruby 2.5 đã giới thiệu String#delete_suffix
sẽ xóa một chuỗi con khỏi phần cuối của một chuỗi. Hai dòng này tương đương nhau, nhưng dòng bên có hiệu suất cao hơn vì nó không dựa vào một chuỗi chung chung regexmatch:
str = 'string_with_suffix'
# bad
str.gsub(/suffix\z/, '')
# good
str.delete_suffix('suffix')
Tự động sửa
Một khía cạnh quan trọng của linters là khả năng tự động khắc phục một số hoặc tất cả các vấn đề đã tìm thấy. asrefactoring một phương pháp lớn. Trong những trường hợp này, không thể tự động hóa.
Quy ước hoặc Cấu hình
Thường có cuộc tranh luận gay gắt trong một cộng đồng hoặc dự án về việc quy định makesense.
Giải pháp truyền thống là cho phép mỗi nhóm giải quyết các cuộc tranh luận với các thành viên bằng cách cho phép họ định cấu hình các quy tắc in linting theo sở thích của riêng họ. không được thực thi ở khắp mọi nơi, ý tưởng chung là loại bỏ hoàn toàn chi phí mà các nhà phát triển có khi tạo kiểu mã. Thay vì thảo luận về độ dài dòng nào phù hợp nhất, mọi người chỉ sử dụng các quy tắc đã được cộng đồng đồng ý.
Trong Ruby, điều này ít nhiều chuyển thành hai linters hiện có:RuboCop, cho phép cấu hình đầy đủ và StandardRB, có cách tiếp cận tổng hợp và xác định một tiêu chuẩn chung.
RuboCop
Sử dụng cách tiếp cận thông thường là cung cấp một bộ quy tắc được lập thành văn bản, mỗi quy tắc tìm kiếm một vấn đề cụ thể. Các nhà phát triển có thể vô hiệu hóa hoặc chỉnh sửa các quy tắc nhất định trong các dự án của riêng họ:
# configure max allowed line length
Layout/LineLength:
Max: 80
# disable cyclomatic complexity
Metrics/CyclomaticComplexity:
Enabled: false
Nó đã bao gồm các mặc định lành mạnh, do đó, chỉ cần cấu hình cho các quy tắc cụ thể mà bạn muốn thay đổi.
Chạy rubocop bundle exec rubocop
sẽ làm cho RuboCop phân tích toàn bộ nền tảng mã và liệt kê tất cả các vấn đề mà nó tìm thấy:
# test.rb
def badName
if something
return "inner result"
end
"outer result"
end
$ bundle exec rubocop
Inspecting 1 file
C
Offenses:
test.rb:1:1: C: [Correctable] Style/FrozenStringLiteralComment: Missing frozen string literal comment.
def badName
^
test.rb:1:5: C: Naming/MethodName: Use snake_case for method names.
def badName
^^^^^^^
test.rb:2:3: C: [Correctable] Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
if something
^^
test.rb:3:12: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
return "inner result"
^^^^^^^^^^^^^^
test.rb:6:3: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
"outer result"
^^^^^^^^^^^^^^
1 file inspected, 5 offenses detected, 4 offenses auto-correctable
Sau đó, bạn có thể chạy bundle exec rubocop --auto-correct
và phần lớn các vấn đề của bạn sẽ được khắc phục theo cấu hình của bạn.
Thiết lập rubocop bundle exec rubocop
như một phần của đường dẫn CI của bạn sẽ đảm bảo không có mã nào đi qua mà không thực hiện các quy tắc linting trước.
StandardRB
Một dự án gần đây hơn thực sự sử dụng RuboCop. Mục tiêu chính của StandardRB không phải là xây dựng một linter hoàn toàn riêng biệt, mà là đưa ra một tiêu chuẩn mà mọi người có thể sử dụng thay vì tranh cãi.
Cuộc nói chuyện chớp nhoáng nơi nó được công bố lần đầu tiên khá rõ ràng về các động lực:Nếu mọi người dành ít thời gian tranh luận về các chi tiết cú pháp và chỉ làm theo các quyết định của một thỏa thuận toàn cộng đồng, tất cả chúng ta có thể dành nhiều thời gian hơn cho những gì thực sự quan trọng:xây dựng các sản phẩm và thư viện tuyệt vời.
Vì RuboCop được sử dụng bên dưới, đầu ra bạn nhận được thực sự ở cùng một định dạng. Điểm khác biệt duy nhất là bạn không được phép tùy chỉnh bất kỳ quy tắc nào.
StandardRB gần đây đã đạt đến 1.0.0, có nghĩa là hầu hết các cuộc thảo luận về các quy tắc nào sẽ sử dụng đã diễn ra trên trang vấn đề của họ. / P>
Tuy nhiên, cuối cùng thì bạn có thể tin rằng nó đã được thảo luận ở một khía cạnh nào đó. Không thể để cả một cộng đồng đồng ý 100% về tất cả các điểm. Triết lý của cách tiếp cận này là mọi người linh hoạt và có thể không đồng ý và đưa ra quyết định.
Lời kết
Sau khi dành nhiều thời gian hơn tôi tự hào về các quy tắc linting nitpicking trong các dự án trước đây, tôi chắc chắn thấy giá trị trong cách tiếp cận của StandardRB và tôi khuyên bạn nên sử dụng nó bất cứ khi nào có thể.
Giữ tất cả các lợi ích của tính nhất quán trong khi loại bỏ các vấn đề chung, cùng với các bản sửa lỗi tự động cho hầu hết các quy tắc giúp chúng tôi phân phối phần mềm tốt hơn hiệu quả hơn và tập trung vào những gì thực sự quan trọng.
Các ngôn ngữ khác đang sử dụng các bộ định dạng mã có cấu hình thấp tương tự. mix formatter
trong Elixir và rustfmt
trong Rust, cả hai đều cho phép một số khả năng định cấu hình, nhưng cộng đồng này đang hoạt động một cách đáng ngạc nhiên với việc duy trì trong tiêu chuẩn.
Với điều đó đã nói, RuboCop vẫn là một lựa chọn hoàn toàn hợp lệ nếu bạn, trớ trêu thay, không đồng ý với quan điểm này.
Tái bút. Nếu bạn muốn đọc các bài đăng của Ruby Magic ngay khi chúng xuất hiện trên báo chí, hãy đăng ký bản tin Ruby Magic của chúng tôi và không bao giờ bỏ lỡ một bài đăng nào!