Linting เป็นกระบวนการของการวิเคราะห์โค้ดแบบคงที่เพื่อค้นหาปัญหาที่อาจเกิดขึ้น
ในกรณีนี้ สิ่งที่ก่อให้เกิดปัญหาอาจแตกต่างกันไปตามภาษาโปรแกรม หรือแม้แต่ข้ามโปรเจ็กต์ในภาษาเดียวกัน ฉันจะใส่ปัญหาเหล่านี้ไว้ในหมวดหมู่ต่างๆ สองสามหมวดหมู่:
- แบบเป็นโปรแกรม
- ความปลอดภัย
- โวหาร
- ประสิทธิภาพ
มาดูตัวอย่างบางส่วนกัน
ปัญหาโวหาร
ไม่มีวิธีการจัดรูปแบบโค้ดที่ถูกต้องตามความเป็นจริง เนื่องจากเป็นเรื่องเกี่ยวกับความชอบของผู้อ่านเท่านั้น กุญแจสำคัญคือความสม่ำเสมอ ประเด็นทั่วไปของการอภิปรายได้แก่:
- เครื่องหมายคำพูดคู่เทียบกับเครื่องหมายคำพูดเดี่ยว
- แท็บเทียบกับช่องว่าง
- ความยาวเส้นสูงสุด
- การเยื้องของการโทรหลายสายดังที่แสดงด้านล่าง:
# always single-line
foo(:a, :b, :c)
# aligned with first argument
foo(:a,
:b,
:c
)
# aligned with function name
foo(
:a,
:b
)
สิ่งเหล่านี้ล้วนแล้วแต่เป็นความเห็นส่วนตัว แต่มักจะเป็นประโยชน์ที่จะตกลงเรื่องมาตรฐานสำหรับแต่ละโปรเจ็กต์ เพื่อให้โค้ดเบสทั้งหมดสอดคล้องกัน
ปัญหาเชิงโปรแกรม
ฉันรวมปัญหาเช่น:
- เนื้อหาวิธีการที่ยาวมาก ซึ่งนำไปสู่ปัญหาเรื่องความสามารถในการอ่านและการบำรุงรักษา
- Cyclomatic Complexity เมตริกที่ใช้กันทั่วไปในการวัดความซับซ้อนของโค้ด
- การมอบหมายงานภายในเงื่อนไข เป็นไปได้มากว่าถ้าคุณพิมพ์
if x = true
คุณหมายถึงif x == true
. และแม้ว่าคุณจะหมายถึงงานที่ได้รับมอบหมาย แต่ก็ยังเป็นแนวทางที่ไม่สัญชาตญาณ
ปัญหาด้านความปลอดภัย
ฟังก์ชันหรือแนวทางปฏิบัติบางอย่างอาจมีปัญหาด้านความปลอดภัยที่นักพัฒนาอาจไม่ทราบ
ตัวอย่างเช่น ใน Ruby Kernel#open
เป็นฟังก์ชันที่ยืดหยุ่นซึ่งช่วยให้เปิดไฟล์หรือ URL ภายนอกได้ แต่ยังอนุญาตให้เข้าถึงระบบไฟล์ตามอำเภอใจด้วยการโทรแปลก ๆ เช่น open("| ls")
ดังนั้นจึงควรเตือนนักพัฒนาเกี่ยวกับเรื่องนี้เพื่อให้สามารถใช้แนวทางที่ปลอดภัยยิ่งขึ้น (File#open
, IO.popen
, URI.parse#open
) หรือตัดสินใจให้แน่ชัดว่าจะต้องรับความเสี่ยงจากพฤติกรรมนั้นเอง
ปัญหาด้านประสิทธิภาพ
มีรายละเอียดมากมายเกี่ยวกับการทำงานภายในของ Ruby ที่ทำให้ตัวเลือกบางอย่างมีประสิทธิภาพมากกว่าตัวเลือกอื่นๆ ขึ้นอยู่กับบริบท
ข้อมูลเล็กๆ น้อยๆ ที่เตือนเราเกี่ยวกับสิ่งเหล่านี้ช่วยให้เราเรียนรู้ไปพร้อมๆ กับการปรับรายละเอียดบางอย่างของโปรแกรมของเราให้เหมาะสม
ตัวอย่างเช่น Ruby 2.5 แนะนำ String#delete_suffix
ซึ่งลบสตริงย่อยออกจากส่วนท้ายของสตริง สองบรรทัดนี้เทียบเท่ากัน แต่บรรทัดหนึ่งมีประสิทธิภาพมากกว่าเนื่องจากไม่พึ่งพา regexmatch สตริงทั่วไป:
str = 'string_with_suffix'
# bad
str.gsub(/suffix\z/, '')
# good
str.delete_suffix('suffix')
แก้ไขอัตโนมัติ
สิ่งสำคัญของ linters คือความสามารถในการแก้ไขปัญหาที่พบบางส่วนหรือทั้งหมดโดยอัตโนมัติ ด้านการจัดรูปแบบ เช่น ความยาวของเส้นจะเป็นไปโดยอัตโนมัติอย่างง่ายดาย ดังนั้นจึงควรที่จะขจัดภาระนั้นออกจากนักพัฒนา ปัญหาอื่นๆ อาจเป็นเรื่องส่วนตัวหรือต้องการการแทรกแซงจากมนุษย์ เช่น asrefactoring วิธีการขนาดใหญ่ ในกรณีเหล่านี้ ระบบอัตโนมัติจะไม่สามารถทำได้
การประชุมหรือการกำหนดค่า
มักจะมีการถกเถียงกันอย่างหนักภายในชุมชนหรือโครงการที่กฎเกณฑ์ต่างๆ เหมาะสม
วิธีแก้ปัญหาแบบดั้งเดิมคือการอนุญาตให้แต่ละทีมแก้ปัญหาการโต้วาทีภายในสมาชิกโดยอนุญาตให้พวกเขากำหนดค่ากฎผ้าสำลีตามรสนิยมของตนเอง อย่างไรก็ตาม ในช่วงไม่กี่ปีที่ผ่านมา มีการผลักดันหลายภาษาเพื่อสร้างมาตรฐานให้เป็นแบบแผนเดียว แม้ว่าจะไม่ใช่ การบังคับใช้ในทุกที่ แนวคิดทั่วไปคือการลบโอเวอร์เฮดตามหัวข้อที่นักพัฒนาซอฟต์แวร์มีเมื่อใส่สไตล์โค้ด แทนที่จะพูดถึงความยาวบรรทัดที่ดีที่สุด ทุกคนก็แค่ใช้กฎที่ตกลงกันโดยชุมชน
ใน Ruby ค่านี้จะแปลเป็นสอง linters ที่มีอยู่:RuboCop ซึ่งอนุญาตให้กำหนดค่าแบบเต็มและ StandardRB ซึ่งใช้แนวทางตรงข้ามและกำหนดมาตรฐานทั่วไป
รูโบคอป
ใช้แนวทางปกติในการจัดทำชุดกฎเกณฑ์ ซึ่งแต่ละข้อจะมองหาปัญหาเฉพาะ นักพัฒนาสามารถปิดใช้งานหรือปรับแต่งกฎบางอย่างภายในโครงการของตนเองได้:
# configure max allowed line length
Layout/LineLength:
Max: 80
# disable cyclomatic complexity
Metrics/CyclomaticComplexity:
Enabled: false
มีค่าเริ่มต้นที่เหมาะสมอยู่แล้ว ดังนั้นการกำหนดค่าจึงจำเป็นสำหรับกฎเฉพาะที่คุณต้องการเปลี่ยนแปลงเท่านั้น
กำลังเรียกใช้ bundle exec rubocop
จะทำให้ RuboCop วิเคราะห์ codebase ทั้งหมดและแสดงรายการปัญหาทั้งหมดที่พบ:
# 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
และปัญหาส่วนใหญ่ของคุณจะได้รับการแก้ไขตามการกำหนดค่าของคุณ
กำลังตั้งค่า bundle exec rubocop
เป็นส่วนหนึ่งของไปป์ไลน์ CI ของคุณเพื่อให้แน่ใจว่าไม่มีโค้ดเก็ตผ่านหากไม่ปฏิบัติตามกฎการขัดถูก่อน
StandardRB
โปรเจ็กต์ล่าสุดที่ใช้ RuboCop จริง ๆ แล้ว เป้าหมายหลักของ StandardRB ไม่ใช่การสร้าง linter ที่แยกจากกันโดยสิ้นเชิง แต่บรรลุมาตรฐานที่ทุกคนสามารถใช้แทนการโต้เถียงได้
การพูดคุยแบบสายฟ้าแลบเมื่อมีการประกาศครั้งแรกนั้นค่อนข้างชัดเจนเกี่ยวกับการสร้างแรงจูงใจ:หากผู้คนใช้เวลาน้อยลงในการโต้เถียงเกี่ยวกับรายละเอียดวากยสัมพันธ์และทำตามการตัดสินใจของข้อตกลงระดับชุมชน เราทุกคนสามารถใช้เวลามากขึ้นในการทำสิ่งที่สำคัญจริงๆ:การสร้างผลิตภัณฑ์และห้องสมุดที่ยอดเยี่ยม
เนื่องจากมีการใช้ RuboCop ด้านล่าง ผลลัพธ์ที่คุณได้รับจึงอยู่ในรูปแบบเดียวกัน ข้อแตกต่างเพียงอย่างเดียวคือคุณไม่ได้รับอนุญาตให้ปรับแต่งกฎใดๆ
StandardRB เพิ่งถึง 1.0.0 ซึ่งหมายความว่าการอภิปรายส่วนใหญ่เกี่ยวกับกฎที่จะใช้ได้เกิดขึ้นแล้วในหน้าปัญหาของพวกเขา หากคุณสนใจหรือไม่เห็นด้วยกับกฎใดกฎหนึ่ง มีโอกาสที่คุณจะเห็นการสนทนาที่เกี่ยวข้องในนั้น
อย่างไรก็ตาม ในท้ายที่สุด คุณสามารถมั่นใจได้ว่าได้มีการพูดคุยกันในเชิงลึก เป็นไปไม่ได้ที่ทั้งชุมชนจะเห็นด้วย 100% ในทุกประเด็น ปรัชญาของแนวทางนี้คือผู้คนมีความยืดหยุ่น ไม่เห็นด้วย และตัดสินใจได้
ความคิดสุดท้าย
หลังจากใช้เวลามากกว่าที่ฉันภาคภูมิใจในการค้นหากฎการนินทาในโปรเจ็กต์ที่ผ่านมา ฉันเห็นคุณค่าในแนวทางของ StandardRB อย่างแน่นอน และฉันขอแนะนำให้ใช้เมื่อมีโอกาส
การรักษาผลประโยชน์ของความสม่ำเสมอทั้งหมดไว้พร้อมๆ กับขจัดส่วนเกินของการอภิปราย ควบคู่ไปกับการแก้ไขอัตโนมัติสำหรับกฎส่วนใหญ่ช่วยให้เราส่งมอบซอฟต์แวร์ที่ดีขึ้นอย่างมีประสิทธิภาพมากขึ้น และมุ่งเน้นไปที่สิ่งที่สำคัญจริงๆ
ภาษาอื่นกำลังใช้ตัวจัดรูปแบบโค้ดที่กำหนดค่าได้ต่ำที่คล้ายคลึงกัน mix formatter
ใน Elixir และ rustfmt
ใน Rust ทั้งคู่อนุญาตให้กำหนดค่าบางอย่างได้ แต่ชุมชนมีความพร้อมอย่างน่าประหลาดใจโดยรักษาให้อยู่ในมาตรฐาน
จากที่กล่าวมา RuboCop ยังคงเป็นตัวเลือกที่ถูกต้องสมบูรณ์หากคุณไม่เห็นด้วยกับความรู้สึกนี้อย่างแดกดัน
ป.ล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่ออกจากสื่อ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!