บัญญัติ 6 ประการของจรรยาบรรณที่ดี: เขียนโค้ดที่ยืนหยัดเหนือกาลเวลา
เผยแพร่แล้ว: 2022-03-11มนุษย์เพิ่งต่อสู้กับศิลปะและวิทยาศาสตร์ของการเขียนโปรแกรมคอมพิวเตอร์มาประมาณครึ่งศตวรรษ เมื่อเทียบกับศิลปะและวิทยาศาสตร์ส่วนใหญ่แล้ว วิทยาการคอมพิวเตอร์ยังเป็นเพียงเด็กเดินเตาะแตะในหลายๆ ด้าน เดินเข้าไปในกำแพง สะดุดขาตัวเอง และบางครั้งก็ขว้างอาหารบนโต๊ะเป็นครั้งคราว
ผลสืบเนื่องมาจากความเยาว์วัยของมัน ฉันไม่เชื่อว่าเรามีฉันทามติว่ายังมีคำจำกัดความที่ถูกต้องของ "โค้ดที่ดี" อย่างไร เนื่องจากคำจำกัดความนั้นยังคงพัฒนาต่อไป บางคนจะบอกว่า “รหัสที่ดี” คือรหัสที่มีการทดสอบครอบคลุม 100% คนอื่นๆ จะบอกว่ามันเร็วสุด ๆ และมีประสิทธิภาพที่เหนือชั้น และจะทำงานได้อย่างยอมรับบนฮาร์ดแวร์อายุ 10 ปี
แม้ว่าสิ่งเหล่านี้เป็นเป้าหมายที่น่ายกย่องสำหรับนักพัฒนาซอฟต์แวร์ แต่ฉันเสี่ยงที่จะโยนเป้าหมายอื่นเข้ามารวมกัน นั่นคือ การบำรุงรักษา โดยเฉพาะอย่างยิ่ง “รหัสที่ดี” คือรหัสที่องค์กรสามารถดูแลรักษาได้ง่ายและพร้อม (ไม่ใช่แค่โดยผู้เขียนเท่านั้น!) และจะมีอายุยืนยาวกว่าแค่การวิ่งที่เขียนขึ้น ต่อไปนี้คือบางสิ่งที่ฉันค้นพบในตัวฉัน อาชีพการเป็นวิศวกรในบริษัทขนาดใหญ่และขนาดเล็ก ในสหรัฐอเมริกาและต่างประเทศ ที่ดูเหมือนจะสัมพันธ์กับซอฟต์แวร์ที่ "ดี" ที่บำรุงรักษาได้
บัญญัติ #1: ปฏิบัติต่อรหัสของคุณในแบบที่คุณต้องการให้รหัสของผู้อื่นปฏิบัติต่อคุณ
ฉันไม่ใช่คนแรกที่เขียนว่าผู้ชมหลักสำหรับโค้ดของคุณไม่ใช่คอมไพเลอร์/คอมพิวเตอร์ แต่ใครก็ตามที่ต้องอ่าน ทำความเข้าใจ บำรุงรักษา และปรับปรุงโค้ดต่อไป (ซึ่งไม่จำเป็นต้องเป็นคุณอีกหกเดือนต่อจากนี้ ). วิศวกรคนใดที่คุ้มค่ากับการจ่ายสามารถสร้างรหัสที่ "ใช้งานได้" สิ่งที่ทำให้วิศวกรที่ยอดเยี่ยมแตกต่างออกไปก็คือ พวกเขาสามารถเขียนโค้ดที่บำรุงรักษาได้อย่างมีประสิทธิภาพ ซึ่งสนับสนุนธุรกิจในระยะยาว และมีทักษะในการแก้ปัญหาอย่างเรียบง่ายและชัดเจนและสามารถบำรุงรักษาได้
ในภาษาการเขียนโปรแกรมใด ๆ เป็นไปได้ที่จะเขียนโค้ดที่ดีหรือโค้ดที่ไม่ดี สมมติว่าเราตัดสินภาษาการเขียนโปรแกรมโดยความสะดวกในการเขียนโค้ดที่ดี (อย่างน้อยก็ควรเป็นหนึ่งในเกณฑ์อันดับต้น ๆ อยู่ดี) ภาษาการเขียนโปรแกรมใด ๆ อาจเป็น "ดี" หรือ "ไม่ดี" ขึ้นอยู่กับวิธีการใช้ (หรือในทางที่ผิด) ).
ตัวอย่างของภาษาที่หลายคนถือว่า "สะอาด" และอ่านได้คือ Python ภาษานั้นบังคับใช้ระเบียบวินัยของพื้นที่สีขาวในระดับหนึ่งและ API ในตัวนั้นมีมากมายและค่อนข้างสอดคล้องกัน ที่กล่าวว่าเป็นไปได้ที่จะสร้างสัตว์ประหลาดที่ไม่สามารถบรรยายได้ ตัวอย่างเช่น สามารถกำหนดคลาสและกำหนด/กำหนดใหม่/กำหนดวิธีการใดๆ และทุกวิธีในคลาสนั้นระหว่างรันไทม์ (มักเรียกว่าการแพตช์ลิง) เทคนิคนี้นำไปสู่ API ที่ไม่สอดคล้องกันอย่างเป็นธรรมชาติและที่แย่ที่สุดคือเป็นไปไม่ได้ที่จะดีบักสัตว์ประหลาด บางคนอาจคิดอย่างไร้เดียงสาว่า “แน่นอน แต่ไม่มีใครทำอย่างนั้น!” น่าเสียดายที่พวกเขาทำ และใช้เวลาไม่นานในการเรียกดู pypi ก่อนที่คุณจะพบไลบรารีจำนวนมาก (และเป็นที่นิยม!) ที่ (ab) ใช้การแพตช์ลิงอย่างกว้างขวางเป็นแกนหลักของ API ของพวกเขา ฉันเพิ่งใช้ไลบรารีเครือข่ายซึ่ง API ทั้งหมดเปลี่ยนแปลงโดยขึ้นอยู่กับสถานะเครือข่ายของออบเจ็กต์ ลองนึกภาพเช่นการเรียก client.connect() และบางครั้งได้รับข้อผิดพลาด MethodDoesNotExist แทนที่จะเป็น HostNotFound หรือ NetworkUnavailable
บัญญัติ #2: รหัสที่ดีสามารถอ่านและเข้าใจได้ง่าย บางส่วนและทั้งหมด
รหัสที่ดีนั้นอ่านและเข้าใจได้ง่ายในบางส่วนและทั้งหมดโดยผู้อื่น (รวมถึงผู้เขียนในอนาคตด้วย โดยพยายามหลีกเลี่ยงกลุ่มอาการ "ฉันเขียนอย่างนั้นจริงๆ หรือเปล่า" )
โดย "บางส่วน" ฉันหมายความว่าถ้าฉันเปิดโมดูลหรือฟังก์ชันบางอย่างในโค้ด ฉันควรจะสามารถเข้าใจว่ามันทำอะไรได้บ้างโดยไม่ต้องอ่านโค้ดเบสที่เหลือทั้งหมดด้วย ควรเป็นไปโดยสัญชาตญาณและจัดทำเอกสารด้วยตนเองให้ได้มากที่สุด
โค้ดที่อ้างอิงรายละเอียดเล็กๆ น้อยๆ อย่างต่อเนื่องซึ่งส่งผลต่อพฤติกรรมจากส่วนอื่นๆ (ที่ดูเหมือนไม่เกี่ยวข้อง) ของ codebase นั้นเหมือนกับการอ่านหนังสือที่คุณต้องอ้างอิงเชิงอรรถหรือภาคผนวกที่ส่วนท้ายของทุกประโยค คุณจะไม่ผ่านหน้าแรก!
ความคิดอื่นๆ เกี่ยวกับความสามารถในการอ่าน "ท้องถิ่น":
โค้ดที่ห่อหุ้มอย่างดีมีแนวโน้มที่จะอ่านง่ายขึ้น โดยแยกข้อกังวลออกจากกันในทุกระดับ
ชื่อมีความสำคัญ เปิดใช้งานการคิดแบบเร็วและแบบช้า 2 วิธีที่สมองสร้างความคิดและใส่ความคิดที่รอบคอบและเป็นจริงลงในชื่อตัวแปรและวิธีการ ไม่กี่วินาทีพิเศษสามารถจ่ายเงินปันผลได้อย่างมีนัยสำคัญ ตัวแปรที่มีชื่อดีสามารถทำให้โค้ดเข้าใจง่ายขึ้นมาก ในขณะที่ตัวแปรที่มีชื่อไม่ดีสามารถนำไปสู่การปลอมแปลงข้อมูลและความสับสนได้
ความฉลาดเป็นศัตรู เมื่อใช้เทคนิค กระบวนทัศน์ หรือการดำเนินการแฟนซี (เช่น รายการความเข้าใจหรือโอเปอเรเตอร์ประกอบ) ให้ระมัดระวังในการใช้งานในลักษณะที่ทำให้โค้ดของคุณอ่านง่าย ขึ้น ไม่ใช่แค่สั้นลง
ความสม่ำเสมอเป็นสิ่งที่ดี ความสม่ำเสมอของรูปแบบ ทั้งในแง่ของการวางเครื่องมือจัดฟันแต่ยังรวมถึงการทำงานด้วย ช่วยเพิ่มความสามารถในการอ่านได้อย่างมาก
การแยกความกังวล โครงการที่กำหนดจะจัดการสมมติฐานที่สำคัญในท้องถิ่นจำนวนนับไม่ถ้วน ณ จุดต่างๆ ในฐานรหัส เปิดเผยแต่ละส่วนของ codebase ให้กับข้อกังวลเหล่านั้นให้น้อยที่สุด สมมติว่าคุณมีระบบการจัดการบุคคลที่บางครั้งวัตถุอาจมีนามสกุลว่าง สำหรับใครก็ตามที่เขียนโค้ดในหน้าที่แสดงวัตถุบุคคล นั่นอาจเป็นเรื่องที่น่าอึดอัดใจจริงๆ! และเว้นแต่คุณจะรักษาคู่มือของ "สมมติฐานที่น่าอึดอัดใจและไม่ชัดเจนที่ codebase ของเรามี" (ฉันรู้ว่าฉันไม่มี) โปรแกรมเมอร์หน้าที่แสดงของคุณจะไม่ทราบว่านามสกุลอาจเป็นโมฆะและอาจจะเขียนโค้ดด้วย ตัวชี้ค่าว่าง ข้อยกเว้นในกรณีที่นามสกุล null case ปรากฏขึ้น แทนที่จะจัดการกับกรณีเหล่านี้ด้วย API และสัญญาที่คิดมาอย่างดีซึ่งส่วนต่างๆ ของ codebase ของคุณใช้เพื่อโต้ตอบกัน
บัญญัติ #3: จรรยาบรรณที่ดีมีเค้าโครงและสถาปัตยกรรมที่คิดมาอย่างดีเพื่อให้รัฐจัดการชัดเจน
รัฐคือศัตรู ทำไม? เนื่องจากเป็นส่วนที่ซับซ้อนที่สุดเพียงอย่างเดียวของแอปพลิเคชันใด ๆ และจำเป็นต้องได้รับการจัดการอย่างรอบคอบและรอบคอบ ปัญหาทั่วไป ได้แก่ ความไม่สอดคล้องกันของฐานข้อมูล การอัปเดต UI บางส่วนซึ่งข้อมูลใหม่ไม่ได้สะท้อนให้เห็นในทุกที่ การดำเนินการที่ไม่เป็นระเบียบ หรือเพียงแค่คำนึงถึงโค้ดที่ซับซ้อนจนทำให้งงด้วย if คำสั่งและสาขาทุกที่ที่นำไปสู่การอ่านยาก และดูแลรักษาโค้ดได้ยากขึ้น การวางสถานะไว้บนแท่นเพื่อรับการดูแลอย่างดีเยี่ยม และความสอดคล้องอย่างยิ่งและไตร่ตรองอย่างถี่ถ้วนเกี่ยวกับวิธีการเข้าถึงและแก้ไขสถานะ ช่วยลดความซับซ้อนของโค้ดเบสอย่างมาก บางภาษา (เช่น Haskell) บังคับใช้สิ่งนี้ในระดับโปรแกรมและวากยสัมพันธ์ คุณจะประหลาดใจที่ความชัดเจนของโค้ดเบสของคุณสามารถปรับปรุงได้มากน้อยเพียงใด หากคุณมีไลบรารีของฟังก์ชันล้วนๆ ที่ไม่เข้าถึงสถานะภายนอก และจากนั้นมีพื้นที่ผิวเล็ก ๆ ของโค้ดเก็บสถานะซึ่งอ้างอิงถึงฟังก์ชันการทำงานบริสุทธิ์ภายนอก

บัญญัติ #4: จรรยาบรรณที่ดีไม่ได้สร้างวงล้อขึ้นมาใหม่ แต่มันอยู่บนไหล่ของยักษ์
ก่อนสร้างวงล้อขึ้นมาใหม่ ให้คิดว่าปัญหาที่คุณกำลังพยายามแก้ไขอยู่บ่อยแค่ไหน หรือฟังก์ชันที่คุณพยายามทำอยู่นั้นเป็นอย่างไร อาจมีบางคนใช้โซลูชันที่คุณสามารถใช้ประโยชน์ได้แล้ว ใช้เวลาในการคิดและค้นคว้าตัวเลือกดังกล่าว หากเหมาะสมและพร้อมใช้งาน
ที่กล่าวว่า ข้อโต้แย้งที่สมเหตุสมผลอย่างยิ่งคือการที่การขึ้นต่อกันไม่ได้มาเพื่อ "ฟรี" โดยไม่มีข้อเสียใดๆ การใช้ไลบรารีของบุคคลที่สามหรือโอเพ่นซอร์สที่เพิ่มฟังก์ชันการทำงานที่น่าสนใจ แสดงว่าคุณกำลังให้คำมั่นสัญญาและต้องพึ่งพาไลบรารีนั้น นั่นเป็นความมุ่งมั่นที่ยิ่งใหญ่ หากเป็นไลบรารีขนาดใหญ่และคุณต้องการฟังก์ชันเพียงเล็กน้อย คุณต้องการรับภาระในการอัปเดตทั้งไลบรารีจริงหรือไม่ ถ้าคุณอัปเกรดเป็น Python 3.x? และยิ่งไปกว่านั้น หากคุณพบจุดบกพร่องหรือต้องการเพิ่มประสิทธิภาพการทำงาน คุณอาจต้องพึ่งพาผู้เขียน (หรือผู้จำหน่าย) ในการจัดหาการแก้ไขหรือเพิ่มประสิทธิภาพ หรือหากเป็นโอเพ่นซอร์ส ให้พบว่าตัวเองอยู่ในตำแหน่งที่จะสำรวจ ( อาจเป็นกอบเป็นกำ) codebase ที่คุณไม่คุ้นเคยกับการพยายามแก้ไขหรือแก้ไขฟังก์ชันการทำงานที่คลุมเครือ
แน่นอนว่ายิ่งใช้รหัสที่คุณพึ่งพาได้ดีเท่าไร โอกาสที่คุณจะต้องทุ่มเทเวลาให้กับการบำรุงรักษาก็จะยิ่งน้อยลงเท่านั้น สิ่งสำคัญที่สุดคือคุณควรทำวิจัยของคุณเองและประเมินว่าจะรวมเทคโนโลยีภายนอกหรือไม่และการบำรุงรักษาที่เทคโนโลยีนั้นจะเพิ่มให้กับสแต็กของคุณมากน้อยเพียงใด
ด้านล่างนี้คือตัวอย่างทั่วไปบางประการของสิ่งที่คุณไม่ควรคิดค้นขึ้นใหม่ในยุคสมัยใหม่ในโครงการของคุณ (เว้นแต่จะเป็นโครงการของคุณ)
ฐานข้อมูล
พิจารณาว่าคุณต้องการ CAP ใดสำหรับโครงการของคุณ จากนั้นเลือกฐานข้อมูลที่มีคุณสมบัติที่เหมาะสม ฐานข้อมูลไม่ได้หมายถึง MySQL อีกต่อไปแล้ว คุณสามารถเลือกจาก:
- SQL แบบแผน "ดั้งเดิม": Postgres / MySQL / MariaDB / MemSQL / Amazon RDS เป็นต้น
- ร้านค้ามูลค่าหลัก: Redis / Memcache / Riak
- NoSQL: MongoDB/คาสแซนดรา
- ฐานข้อมูลที่ โฮสต์: AWS RDS / DynamoDB / AppEngine Datastore
- ยกของหนัก: Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
- เรื่องบ้าๆบอ ๆ: Mnesia ของ Erlang, Core Data ของ iOS
Data Abstraction Layers
ในกรณีส่วนใหญ่ คุณไม่ควรเขียนแบบสอบถามดิบไปยังฐานข้อมูลใดก็ตามที่คุณเลือกใช้ มีแนวโน้มมากกว่าที่จะมีไลบรารีอยู่ระหว่างฐานข้อมูลและโค้ดแอปพลิเคชันของคุณ โดยแยกข้อกังวลในการจัดการเซสชันฐานข้อมูลพร้อมกันและรายละเอียดของสคีมาออกจากโค้ดหลักของคุณ อย่างน้อยที่สุด คุณไม่ควรมีการสืบค้นข้อมูลดิบหรืออินไลน์ของ SQL ตรงกลางรหัสแอปพลิเคชันของคุณ ให้รวมไว้ในฟังก์ชันและรวมฟังก์ชันทั้งหมดในไฟล์ที่เรียกว่าสิ่งที่ชัดเจนจริงๆ (เช่น "queries.py") ตัวอย่างเช่น บรรทัดที่คล้ายกับ users = load_users() อ่านง่ายกว่า users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”) การรวมศูนย์ประเภทนี้ยังช่วยให้มีสไตล์ที่สอดคล้องกันในการสืบค้นของคุณได้ง่ายขึ้น และจำกัดจำนวนสถานที่ที่จะเปลี่ยนการสืบค้นหากสคีมาเปลี่ยนแปลง
ไลบรารีและเครื่องมือทั่วไปอื่นๆ ที่ควรพิจารณาในการใช้ประโยชน์
- บริการจัดคิวหรือผับ/ย่อย เลือกผู้ให้บริการ AMQP, ZeroMQ, RabbitMQ, Amazon SQS
- พื้นที่จัดเก็บ. Amazon S3, Google Cloud Storage
- การ ตรวจสอบ: Graphite/Hosted Graphite, AWS Cloud Watch, New Relic
- การรวบรวมบันทึก / การรวม Loggly, Splunk
ปรับขนาดอัตโนมัติ
- ปรับขนาดอัตโนมัติ Heroku, AWS Beanstalk, AppEngine, AWS Opsworks, มหาสมุทรดิจิทัล
บัญญัติ #5: อย่าข้ามลำธาร!
มีโมเดลดีๆ มากมายสำหรับการออกแบบโปรแกรม, ผับ/ย่อย, นักแสดง, MVC เป็นต้น เลือกอันที่คุณชอบที่สุดและยึดตามนั้น ตรรกะประเภทต่างๆ ที่เกี่ยวข้องกับข้อมูลประเภทต่างๆ ควรแยกออกจากกันใน codebase (อีกครั้ง การแยกแนวคิดเกี่ยวกับข้อกังวลนี้ โค้ดที่อัปเดต UI ของคุณควรแตกต่างจากโค้ดที่คำนวณสิ่งที่จะเข้าสู่ UI เป็นต้น
บัญญัติ #6: เมื่อเป็นไปได้ ให้คอมพิวเตอร์ทำงาน
หากคอมไพเลอร์สามารถตรวจจับข้อผิดพลาดเชิงตรรกะในโค้ดของคุณ และป้องกันพฤติกรรมที่ไม่ดี ข้อบกพร่อง หรือการขัดข้องทันที เราควรใช้ประโยชน์จากสิ่งนั้นอย่างเต็มที่ แน่นอนว่าบางภาษามีคอมไพเลอร์ที่ทำให้สิ่งนี้ง่ายกว่าภาษาอื่น ตัวอย่างเช่น Haskell มีคอมไพเลอร์ที่เข้มงวดซึ่งส่งผลให้โปรแกรมเมอร์ใช้ความพยายามส่วนใหญ่เพียงแค่รับโค้ดเพื่อคอมไพล์ เมื่อคอมไพล์แล้ว "มันก็ใช้ได้" สำหรับผู้ที่ไม่เคยเขียนด้วยภาษาที่ใช้งานได้จริง อาจดูไร้สาระหรือเป็นไปไม่ได้ แต่อย่าเชื่อคำพูดของฉัน คลิกที่ลิงค์เหล่านี้อย่างจริงจัง เป็นไปได้อย่างยิ่งที่จะอยู่ในโลกที่ไม่มีข้อผิดพลาดรันไทม์ และมันก็วิเศษจริงๆ
เป็นที่ยอมรับว่าไม่ใช่ทุกภาษาที่มีคอมไพเลอร์หรือไวยากรณ์ที่ยืมตัวเองไปมาก (หรือในบางกรณี!) การตรวจสอบเวลาคอมไพล์ สำหรับผู้ที่ไม่ทำอย่างนั้น ใช้เวลาสองสามนาทีเพื่อค้นหาว่าการตรวจสอบความเข้มงวดทางเลือกใดบ้างที่คุณสามารถเปิดใช้งานในโครงการของคุณและประเมินว่าสิ่งเหล่านี้เหมาะสมสำหรับคุณหรือไม่ รายการสั้นๆ ที่ไม่ครอบคลุมของรายการทั่วไปบางรายการที่ฉันใช้เมื่อเร็วๆ นี้สำหรับภาษาที่มีรันไทม์ผ่อนปรน ได้แก่:
- Python: pylint, pyflakes, คำเตือน, คำเตือนใน emacs
- ทับทิม: คำเตือน
- JavaScript: jslint
บทสรุป
นี่ไม่ใช่รายการบัญญัติที่ละเอียดถี่ถ้วนหรือสมบูรณ์แบบสำหรับการสร้างโค้ด "ดี" (กล่าวคือ บำรุงรักษาง่าย) ที่กล่าวว่าหาก codebase ทุกตัวที่ฉันเคยต้องหยิบใช้ในอนาคตทำตามแนวคิดในรายการนี้เพียงครึ่งเดียว ฉันจะมีผมหงอกน้อยลงและอาจเพิ่มเวลาอีกห้าปีในบั้นปลายของชีวิต และฉันจะพบว่างานสนุกขึ้นและเครียดน้อยลงอย่างแน่นอน
