Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญคุณภาพโค้ด Ruby ด้วย Linting ที่มีประสิทธิภาพ

Linting คือกระบวนการวิเคราะห์โค้ดแบบ Static เพื่อค้นหาปัญหาที่อาจเกิดขึ้น

สิ่งที่ถือเป็น "ปัญหา" ในกรณีนี้ อาจแตกต่างกันไปในแต่ละภาษาโปรแกรมมิ่ง หรือแม้แต่ในโปรเจกต์ต่างๆ ภายในภาษาเดียวกัน ฉันจะจัดกลุ่มปัญหาเหล่านี้ไว้ในหมวดหมู่หลักๆ ดังนี้:

  • Programmatic (เกี่ยวกับโปรแกรม)
  • Security (ความปลอดภัย)
  • Stylistic (สไตล์)
  • Performance (ประสิทธิภาพ)

มา看看ตัวอย่างของแต่ละประเภทกัน

ปัญหาด้านสไตล์ (Stylistic Problems)

ไม่มีวิธีที่ "ถูกต้อง" แบบ Objectively ในการจัดสไตล์โค้ด เพราะมันขึ้นอยู่กับความชอบของผู้อ่าน สิ่งสำคัญคือ Consistency (ความสอดคล้อง) จุดโต้แย้งทั่วไปรวมถึง:

  1. Double-quotes vs Single-quotes
  2. Tabs vs Spaces
  3. ความยาวบรรทัดสูงสุด
  4. Indentation ของ Multiline Calls ดังตัวอย่างด้านล่าง:
# always single-line foo(:a, :b, :c) # aligned with first argument foo(:a, :b, :c ) # aligned with function name foo( :a, :b )

สิ่งเหล่านี้เป็นเรื่อง Subjective ทั้งหมด แต่ปกติแล้วมีประโยชน์ที่จะตกลงกัน على Standard สำหรับแต่ละโปรเจกต์เฉพาะ เพื่อรักษา Codebase ให้ Consistent

ปัญหาด้านโปรแกรม (Programmatic Problems)

ฉันรวมปัญหาเช่นนี้ไว้ที่นี่:

  • Method Body ที่ยาวเกินไป ซึ่งนำไปสู่ปัญหา Readability และ Maintainability
  • Cyclomatic Complexity ซึ่งเป็น Metric ที่ใช้วัดความซับซ้อนของโค้ดทั่วไป
  • Assignment ภายใน Condition น่าจะเป็นไปได้มากที่สุด หากคุณพิมพ์ if x = true คุณน่าจะหมายถึง if x == true และแม้คุณจะหมายถึง Assignment จริงๆ มันก็ยังเป็นวิธีที่ Less-intuitive

ปัญหาด้านความปลอดภัย (Security Problems)

Function หรือ Practice บางอย่างมีปัญหาความปลอดภัยที่ Developer อาจไม่รู้จัก

ตัวอย่างเช่น ใน Ruby Kernel#open เป็น Function ที่ยืดหยุ่น อนุญาตให้เปิดไฟล์หรือ External URLs แต่ยังอนุญาตให้เข้าถึง Filesystem แบบ Arbitrary ด้วย การเรียกที่แปลกประหลาดเช่น open("| ls") ดังนั้น จึงสมเหตุสมผลที่จะเตือน Developer เรื่องนี้ เพื่อให้พวกเขาสามารถใช้วิธีที่ปลอดภัยกว่า (File#open, IO.popen, URI.parse#open) หรือตัดสินใจอย่างชัดเจนที่จะรักษาระยะการทำงานนั้นๆ บนความเสี่ยงของตนเอง

ปัญหาด้านประสิทธิภาพ (Performance Problem)

มีรายละเอียดมากมายเกี่ยวกับการทำงานภายในของ Ruby ที่ทำให้บางตัวเลือกมีประสิทธิภาพมากกว่าอื่นๆ ขึ้นอยู่กับ Context

Linter ที่เตือนเราเกี่ยวกับสิ่งเหล่านี้ช่วยให้เราเรียนรู้ไปพร้อมๆ กับการเพิ่มประสิทธิภาพรายละเอียดบางอย่างของโปรแกรม

ตัวอย่างเช่น Ruby 2.5 Introduce String#delete_suffix ซึ่งลบ Substring จากท้ายของ String สองบรรทัดนี้เทียบเท่ากัน แต่อันหลังมีประสิทธิภาพมากกว่า เพราะไม่พึ่งพา Generic String Regex Match:

str = 'string_with_suffix' # bad str.gsub(/suffix\z/, '') # good str.delete_suffix('suffix')

Auto Fixing (การแก้ไขอัตโนมัติ)

ด้านสำคัญของ Linters คือความสามารถในการ Auto-fix บางหรือทั้งหมดของ Issue ที่พบ ด้านสไตล์เช่นความยาวบรรทัด Automate ได้ง่าย ดังนั้นจึงสมเหตุสมผลที่จะเอาภาระนั้นออกจาก Developer Issue อื่นๆ อาจเป็น Subjective หรือต้องการ Human Intervention เช่น Refactor Method ใหญ่ ในกรณีเหล่านี้ Automation ไม่เป็นไปได้

Convention หรือ Configuration (ข้อตกลงหรือการกำหนดค่า)

มักมีการโต้เถียงอย่างหนักภายใน Community หรือโปรเจกต์เกี่ยวกับว่า Rule ไหนมีเหตุผล

วิธีแก้ปัญหาแบบดั้งเดิมคืออนุญาตให้แต่ละทีมแก้ไขการโต้แย้งภายในสมาชิกโดยการอนุญาตให้พวกเขา Config Linting Rules ตามความชอบของตัวเอง อย่างไรก็ตาม ในไม่กี่ปีที่ผ่านมา มีการผลักดันข้ามหลายภาษาเพื่อ Standardize ให้เป็น Convention เดียว แม้จะไม่ได้ Enforce ทุกที่ แนวคิดทั่วไปคือการกำจัด Mental Overhead ที่ Developer มีเมื่อจัดสไตล์โค้ด แทนที่จะโต้แย้งว่าความยาวบรรทัดไหนดีที่สุด ทุกคนเพียงใช้ Rules ที่ Community ตกลงกันไว้

ใน Ruby นี่ कमोबช์ แปลได้เป็น Linter 2 ตัวที่มีอยู่: RuboCop ซึ่งอนุญาต Full-configuration และ StandardRB ซึ่งใช้แนวทางตรงข้ามและกำหนด Standard แบบร่วมกัน

RuboCop

ใช้แนวทางปกติในการ提供 Set ของ Rules ที่มีเอกสาร แต่ละ Rule ค้นหาปัญหาเฉพาะ Developer สามารถ Disable หรือ Tweak Rules บางอย่างในโปรเจกต์ของตนเองได้:

# configure max allowed line length Layout/LineLength: Max: 80 # disable cyclomatic complexity Metrics/CyclomaticComplexity: Enabled: false

มันมี Sane Defaults อยู่แล้ว ดังนั้น Configuration จึงจำเป็นเฉพาะ Rules เฉพาะที่คุณต้องการเปลี่ยน

การรัน bundle exec rubocop จะทำให้ RuboCop วิเคราะห์ Codebase ทั้งหมดและรายการ Issue ทั้งหมดที่พบ:

# 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

จากนั้นคุณสามารถรัน bundle exec rubocop --auto-correct และส่วนใหญ่ของ Issue จะถูกแก้ไขตาม Configuration ของคุณ

การตั้งค่า bundle exec rubocop เป็นส่วนหนึ่งของ CI Pipeline จะทำให้ไม่มีโค้ดผ่านไปโดยไม่ผ่าน Linting Rules ก่อน

StandardRB

โปรเจกต์ที่ใหม่กว่า ซึ่งใช้ RuboCop ภายใต้ฮุ๊ด จุดมุ่งหมายหลักของ StandardRB ไม่ใช่การสร้าง Linter แยกต่างหาก แต่เป็นการทำ Standard ที่ทุกคนสามารถใช้แทนการโต้แย้งกัน

Lightning Talk ที่ประกาศครั้งแรกชัดเจนเกี่ยวกับแรงจูงใจ: หากคนใช้เวลาน้อยลงในการโต้แย้งรายละเอียด Syntax และเพียงปฏิบัติตามข้อตกลงของ Community-wide เราทุกคนสามารถใช้เวลามากขึ้นทำสิ่งที่สำคัญจริงๆ: สร้าง Product และ Library ที่ยอดเยี่ยม

เนื่องจาก RuboCop ถูกใช้ภายใต้ฮุ๊ด Output ที่คุณได้จริงๆ อยู่ใน Format เดียวกัน ความแตกต่างคือคุณไม่ได้รับอนุญาตให้ Customize Rules ใดๆ

StandardRB เพิ่งถึง 1.0.0 ซึ่งหมายความว่าการโต้แย้งเกี่ยวกับ Rule ไหนควรใช้ เกิดขึ้นส่วนใหญ่บน Issues Page ของพวกเขาแล้ว หากคุณแคร์หรือไม่เห็นด้วยกับ Rule ไหน โอกาสคือคุณจะเห็น Discussion เกี่ยวกับมันที่นั่น

อย่างไรก็ตาม สุดท้ายคุณมั่นใจได้ว่ามันถูกโต้แย้งในระดับลึก ไม่สามารถให้ Community ทั้งหมด Agree 100% บนทุกจุดได้ ปรัชญาของแนวทางนี้คือคนมีความยืดหยุ่นและสามารถ Disagree และ Commit ต่อ Decision ได้

ความคิดเห็นสุดท้าย

หลังจากใช้เวลามากกว่าที่ฉันภูมิใจในการ Nitpick Linting Rules ในโปรเจกต์เก่าๆ ฉันแน่นอนว่ามองเห็นคุณค่าในแนวทางของ StandardRB และแนะนำให้ใช้เมื่อเป็นไปได้

การรักษาข้อดีของ Consistency ทั้งหมด ในขณะที่กำจัด Overhead ของการโต้แย้ง พร้อมกับ Auto-fix สำหรับ Rules ส่วนใหญ่ ช่วยให้เราส่งมอบ Software ที่ดีขึ้นอย่างมีประสิทธิภาพ และ Focus บนสิ่งที่สำคัญจริงๆ

ภาษาอื่นๆ กำลังนำ Code Formatters ที่ Low-configurability แบบนี้มาใช้ mix formatter ใน Elixir และ rustfmt ใน Rust ทั้งคู่อนุญาต Configurability บ้าง แต่ Community ค่อนข้าง Onboard กับการอยู่ภายใน Standard

ด้วย ذلك RuboCop ยังคงเป็นตัวเลือกที่ถูกต้องสมบูรณ์ หากคุณ Irony อยู่กับ Sentiment นี้

P.S. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับข่าวสาร Ruby Magic newsletter และจะไม่พลาดโพสต์เด็ดๆ ไปเลย!