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

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

Linting คือการตรวจสอบ Source Code อัตโนมัติเพื่อหา Programmatic และ Stylistic Errors โดย Static Code Analysis Tool เรียกว่า Linter ส่วน Code Formatter เป็น Tool ที่จัด Format Source Code ให้ตรงตาม Rules ที่ตั้งไว้ Linter มัก Report Violations แต่มัก Programmer ต้อง Fix เอง ส่วน Formatter มัก Apply Rules โดยตรงแก้ Formatting Mistakes อัตโนมัติ

การสร้าง Consistent Code Style ใน Project มักต้องแยก Linting และ Formatting Tools แต่อาจมี Single Tool ทำได้ทั้งคู่ ตัวอย่างดีคือ RuboCop ซึ่งเราจะเจาะลึกในบทความนี้ จะเรียนรู้ Setup ใน Ruby Project, ปรับ Config ให้ Output ตรงใจ, Integrate ใน Local Development และ CI Workflow

Installing RuboCop (การติดตั้ง)

ติดตั้งผ่าน RubyGems ง่ายๆ:

$ gem install rubocop

เช็ค Version:

$ rubocop --version
1.18.3

ถ้าชอบ Bundler ใส่ใน Gemfile แล้ว bundle install require: false บอก Bundler.require อย่า Require Gem นี้ใน Code เพราะใช้แค่ Command Line:

gem 'rubocop', require: false

เช็ค Version:

$ bundle exec rubocop --version
1.18.3

Running RuboCop (การรัน)

รันด้วย Default Settings:

$ bundle exec rubocop
$ bundle exec rubocop src/lib

ไม่ส่ง Arguments = Check ทุก Ruby Files ใน Current Dir และ Subdirectories หรือส่ง List Files/Dirs ที่ต้องการ Analyze

ไม่มี Config RuboCop Enforce Guidelines จาก Community-driven Ruby Style Guide หลังรันอาจได้ Errors (Offenses) แต่ละ Offense มี Info ครบ: Description, File, Line Number

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

ท้าย Report จะมี Summary: Inspected Files, Total Offenses, Auto-fixable Count ใส่ -a หรือ --auto-correct RuboCop จะ Auto-correct ปัญหาที่ [Correctable]:

$ bundle exec rubocop -a

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

Corrected Offenses จะ Prefix [Corrected] มี Summary จำนวนที่ Corrected บาง Offense Correctable แต่ไม่ Auto-fix แม้ -a เพราะอาจเปลี่ยน Semantics นิดหน่อย RuboCop ถือว่า Unsafe ถ้าอยาก Fix ทั้ง Unsafe ใช้ -A หรือ --auto-correct-all:

$ bundle exec rubocop -A

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

Rule of Thumb: รัน Test Suite หลัง Auto-correct เพื่อให้ Code Behavior ไม่เปลี่ยนไปอย่างไม่คาดคิด

Configuring RuboCop (การตั้งค่า)

Config ผ่าน .rubocop.yml ที่ Project Root หากอยาก Config เดียวกันทุก Projects ให้ Global Config ใน Home Dir (~/.rubocop.yml) หรือ XDG Config Dir (~/.config/rubocop/config.yml) Global Config จะถูกใช้ถ้าไม่เจอ Local Config ใน Current/Parent Directories

Default Config อยู่ที่ ~/.config/rubocop/default.yml Config อื่นๆ Inherit จากมัน หมายถึง Project Config ต้อง Override แค่ส่วนที่ต่างจาก Default เช่น Enable/Disable Cops หรือ เปลี่ยน Behavior

RuboCop เรียกแต่ละ Check ว่า Cops แต่ละ Cop Detect Offense เฉพาะ Group เป็น Departments:

  • Style: Ruby Style Guide, Check Consistency
  • Layout: Formatting Issues เช่น Whitespace
  • Lint: Possible Errors คล้าย ruby -w แต่ Checks เพิ่ม
  • Metrics: Code Measurements เช่น Class/Method Length
  • Naming: Naming Conventions
  • Security: Potential Security Issues
  • Bundler: Bad Practices ใน Gemfile
  • Gemspec: Bad Practices ใน .gemspec

ยัง Extend RuboCop ได้ด้วย Additional Linters/Formatters สร้างเองหรือใช้ Existing Ones เช่น Rails Extension สำหรับ Rails Best Practices

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

สร้าง Config File ครั้งแรก จะได้ Warning ใหญ่ๆ เกี่ยวกับ New Cops ที่ Added แต่ยังไม่ Configured เพราะ RuboCop Add New Cops ทุก Release ตั้งค่า Pending จนกว่า User จะ Enable/Disable ให้ Enable All New Cops (แนะนำ) ด้วย:

# .rubocop.yml
AllCops:
  NewCops: enable

ถ้าไม่อยาก Configure เอง ลอง Standard Project = Pre-configured RuboCop Enforce Consistent Style ไม่ให้ Customize Rules Install:

# Gemfile
gem "standard", group: [:development, :test]
$ bundle exec standardrb

Adding RuboCop to Existing Project (เพิ่มในโปรเจกต์ที่มีอยู่)

Rubyists ส่วนใหญ่ไม่ได้ทำ Greenfield Projects เยอะ Legacy Codebases อาจมี Offenses เยอะมาก RuboCop มี Feature สร้าง Allowlist Existing Offenses จัดการทีละน้อยได้ Benefit: Introduce Linting โดยไม่ถูกท่วม Errors และ Flag New Violations Going Forward

$ bundle exec rubocop
523 files inspected, 1018 offenses detected

สร้าง Allowlist Config:

$ bundle exec rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Created .rubocop_todo.yml.

--auto-gen-config Collect Offenses และ Counts Generate .rubocop_todo.yml Ignore Current Offenses ทั้งหมด แล้วทำให้ .rubocop.yml Inherit จากมัน รันใหม่จะได้ No Offenses

$ bundle exec rubocop
523 files inspected, no offenses detected

ขณะ Generate Allowlist RuboCop จะ Disable Cop เลยถ้า Violations > Threshold (Default 15) ไม่ดีเพราะ New Code จะไม่ถูก Check ครับ Cop นั้นเพราะ Existing Violations มาก Fortunate: เพิ่ม Threshold ได้

$ bundle exec rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000

--auto-gen-only-exclude ให้แต่ละ Cop ใน Allowlist มี Exclude Block List Files ที่ Violation แทน Max --exclude-limit เปลี่ยน Max Files ใน Exclude Block ตั้งเลขใหญ่กว่า Total Files = ไม่ Disable Cops ใดๆ New Code จะถูก Check ตามปกติ

Fixing Existing Violations (แก้ Violations ที่มีอยู่)

หลัง Generate .rubocop_todo.yml อย่าลืม Violations เดิม แก้ทีละน้อย: ลบ File ออกจาก Exclude Block ของ Cop -> Fix Violations -> Run Tests -> Commit ลบทุก Files ของ Cop แล้ว ลบ Cop ออกจาก File หรือ Regenerate Allowlist ใหม่ ใช้ --auto-correct เร็วขึ้น

Adopting a Style Guide (ใช้ Style Guide สำเร็จรูป)

RuboCop Configurable มาก Viable สำหรับทุก Project แต่อาจ Config นานหรือไม่เห็นด้วย Default Rules มาก การ Adopt Existing Style Guide ช่วยได้ บริษัทหลายแห่ง Release Ruby Style Guides Public เช่น Shopify, Airbnb ใช้ใน RuboCop โดยเพิ่ม Gem ใน Gemfile:

# Gemfile
gem "rubocop-shopify", require: false

Require ใน Project Config:

# .rubocop.yml
inherit_gem:
  rubocop-shopify: rubocop.yml

Suppressing Linting Errors (ปิด Linting Errors)

RuboCop อาจ False Positives หรือ Suggest Fix ที่ทำลาย Intent ของ Programmer ใช้ Comment ใน Source Code Ignore Violation:

# rubocop:disable Layout/LineLength, Style
[..]
# rubocop:enable Layout/LineLength, Style

หรือ Disable All Cops ใน Section:

# rubocop:disable all
[..]
# rubocop:enable all

End-of-line Comment Disable Cops บรรทัดนั้นอย่างเดียว:

for x in (0..10) # rubocop:disable Style/For

Editor Integration (การผสานรวมกับ Editor)

ดู Warnings/Errors ขณะ Type Code ใน Editor สะดวกกว่า Run CLI ทุกครั้ง RuboCop Integration มีใน Popular Editors/IDEs ผ่าน Plugins ใน VS Code: Install Ruby Extension ใส่ใน User settings.json:

{
  "ruby.lint": {
    "rubocop": true
  }
}

Vim/Neovim: ใช้ coc.nvim Install Solargraph Language Server (gem install solargraph) แล้ว coc-solargraph Extension (:CocInstall coc-solargraph) Config coc-settings.json:

{
  "coc.preferences.formatOnSaveFiletypes": ["ruby"],
  "solargraph.autoformat": true,
  "solargraph.diagnostics": true,
  "solargraph.formatting": true
}

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

Setting Up a Pre-commit Hook (Pre-commit Hook)

วิธีดีให้ Ruby Code ทั้งหมด Linted/Formatted ก่อน Commit = Git Pre-commit Hook รัน RuboCop บน Staged Files ใช้ Overcommit (Tool Manage Git Hooks) แต่ Integrate กับ Tools อื่นได้ถ้ามี Workflow อยู่แล้ว

Install Overcommit:

$ gem install overcommit
$ overcommit --install # at project root

คำสั่งที่ 2 สร้าง .overcommit.yml (Repo-specific Settings) Backup Existing Hooks File Extends Default Config ต้อง Specify Config เทียบ Defaults เช่น Enable RuboCop Pre-commit Hook:

# .overcommit.yml
PreCommit:
  RuboCop:
    enabled: true
    on_warn: fail
    problem_on_unmodified_line: ignore
    command: ['bundle', 'exec', 'rubocop']

on_warn: fail ให้ Overcommit Treat Warnings As Failures problem_on_unmodified_line: ignore Ignore Warnings/Errors บน Lines ที่ไม่ Staged Browse Hook Options บน GitHub Project รัน overcommit --sign หลังแก้ Config

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

อยาก Commit File ที่ไม่ Pass Checks (WIP) Skip Individual Checks Case-by-case:

$ SKIP=RuboCop git commit -m "WIP: Unfinished work"

Adding RuboCop to CI Workflow (เพิ่มใน CI Workflow)

รัน RuboCop Checks ทุก Pull Request ป้องกัน Badly Formatted Code Merge ใช้ GitHub Actions สร้าง .github/workflows/rubocop.yml:

# .github/workflows/rubocop.yml
name: Lint code with RuboCop
on: [push, pull_request]
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]
    steps:
      - uses: actions/checkout@v2
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
          bundler-cache: true
      - name: Run RuboCop
        run: bundle exec rubocop

Workflow: Job Unique รันเมื่อ Push หรือ PR รันบน Ubuntu, MacOS, Windows Latest (Matrix) Steps: Checkout -> Setup Ruby/Dependencies -> Run RuboCop Push ไป GitHub ได้ Inline Display Issues บน Check-ins/PRs ต่อไป

ปรับปรุงคุณภาพโค้ด Ruby: Linting และ Auto-Formatting ด้วย RuboCop

Alternative Auto-formatters (Auto-formatters ทางเลือก)

RuboCop Comprehensive แต่อาจไม่ตอบโจทย์ ทราบ Alternative Tools เผื่อไว้

Prettier

Prettier เริ่มต้น Opinionated Formatter สำหรับ JavaScript ตอนนี้ Support หลายภาษา รวม Ruby Install Ruby Plugin: Add prettier Gem -> bundle

# Gemfile
gem 'prettier'

Format Ruby Code ด้วย Prettier:

$ bundle exec rbprettier --write '**/*.rb'

Rules บางอย่าง Conflict กับ RuboCop ต้อง Disable Formatting Checks ของ RuboCop ไม่ให้ Interfere ใช้ Prettier Config Inherit ที่บนสุด .rubocop.yml:

# .rubocop.yml
inherit_gem:
  prettier: rubocop.yml

รัน RuboCop ต่อไปจะไม่ Report Layout Offenses ให้ Prettier Fix ตาม Rules มัน Config Prettier Output ผ่าน Config File ร่วมกับ JS/Ruby ใน Project เดียวกันได้

RubyFmt

RubyFmt = Code Formatter ใหม่ เขียน Rust Active Development คล้าย Prettier เป็น Formatter ไม่ใช่ Analysis Tool ยังไม่ Stable Release ควรรอแต่ติดตามไว้

Conclusion (สรุป)

Linting และ Auto-formatting ให้ Benefits มากใน Codebase โดยเฉพาะ Team Developers แม้คุณจะไม่ชอบถูกบอก Format Code Linting ไม่ได้แค่สำหรับคุณ แต่สำหรับ Collaborators ให้ Everyone Stick Same Conventions Eliminate Drawbacks ของ Multiple Coding Styles ใน Project เดียวกัน

สำคัญ: อย่า Treat Linter Output ว่า Gospel ตั้ง Config ให้ Benefits มากที่สุดโดยไม่ Distract Objectives RuboCop Configurable มากควรไม่เป็นปัญหา แต่ถ้า Config นานเกินไป ใช้ Predefined Style Guide หรือ Adopt Standard (No-config Alternative) ทุกคนใช้ได้เลยไม่ต้องกังวลรายละเอียดเล็กๆ

ขอบคุณที่อ่าน Happy Coding!