บัญญัติ 6 ประการ ของการเขียน Code ที่ดี เพื่อให้ใช้งานได้ยาวนาน

26-ก.ค.-19

คัมภีร์เทพ IT

เมื่อมีคนถามว่า “Code ที่ดี” คืออะไร แต่ละคนก็คงให้คำนิยามแตกต่างกันไป เช่น ควรเป็น Code ที่สามารถใช้งานได้ยาวนาน, ต้องเร็วมากและมีประสิทธิภาพที่ยอดเยี่ยม รวมทั้งควรทำงานได้ดีกับ Hardware ที่อายุยาวนานเป็น 10 ปีได้ และที่สำคัญคือ ควร Maintain ได้อย่างง่ายดายและรวดเร็ว วันนี้เรามาดู บัญญัติ 6 ประการ ของการเขียน Code ที่ดี เพื่อให้ใช้งานได้ยาวนาน จากคุณ ZACHARY GOLDBERG กัน

บัญญัติข้อที่ 1: เขียน Code ให้เหมือนกับที่คุณต้องการให้คนอื่นเขียน

Zachary กล่าวว่า เขายังห่างไกลจากการเป็น บุคคลแรกที่เขียนว่า Audience กลุ่มแรกสำหรับ Code ของคุณ ไม่ใช่ Compiler/Computer แต่เป็นคนอื่น ๆ ที่ต้องอ่าน, ทำความเข้าใจ, Maintain และปรับปรุง Code (ซึ่งไม่จำเป็นต้องเป็นคุณ)  Engineer ที่มีเก่ง ๆ จะสามารถสร้าง Code ที่ “ทำงานได้ดี” พวกเขาสามารถเขียน Code ที่สามารถ Maintain ได้อย่างมีประสิทธิภาพซึ่งสนับสนุนธุรกิจในระยะยาว และมีทักษะในการแก้ปัญหาได้อย่างง่ายดายและในแนวทางที่มีความชัดเจนและ Maintain ได้ง่าย

ไม่ว่า Programming Language ใด ก็มีโอกาสที่จะเขียน Code ที่ดี หรือ ไม่ดี ก็ได้ ตัวอย่างของภาษาที่หลาย ๆ คนคิดว่ามัน "Clean" และสามารถอ่านได้ง่าย คือ Python ในเป็นภาษาซึ่งต้องมีวินัยในการใช้ White Space ในระดับหนึ่ง และ API ที่สร้างขึ้นนั้นมีความครบถ้วนสมบูรณ์และสอดคล้องกัน ที่กล่าวมามันเป็นไปได้ที่จะสร้าง Code ที่ยากแก่การเข้าใจ ตัวอย่างเช่น มันสามารถ Define Class และ Define / Redefine / Undefine ทั้งในบาง Method และทุกๆ Method ในระหว่าง Runtime (มักจะเรียกว่า Monkey Patching) เทคนิคนี้อาจนำไปสู่การสร้าง API ที่ไม่สอดคล้องกัน และไม่สามารถ Debug Code ได้ ส่วนหนึ่งอาจคิดว่า “แน่นอน คงไม่มีใครทำอย่างนั้นหรอก!" แต่โชคไม่ดีที่มีคนทำและใช้เวลาไม่นานนักในการเรียกดู pypi ก่อนที่คุณจะ Run ใน Library ที่เหมาะสม (และเป็นที่นิยม!) ซึ่งใช้ Monkey Patching (แบบผิด ๆ) เป็นส่วนสำคัญใน API ของพวกเขา สำหรับ Zachary เองก็เพิ่งใช้ Networking Library ที่มีการเปลี่ยนแปลง API ทั้งหมดขึ้นอยู่กับ Network State ของ Object ลองนึกภาพเช่น การเรียก client.connect() และบางครั้งได้รับ Error MethodDoesNotExist แทนที่จะเป็น HostNotFound หรือ NetworkUnavailable

บัญญัติข้อที่ 2:  Code ที่ดี ควรอ่านและเข้าใจได้ง่ายในทุก ๆ ส่วน

Code ที่ดี สามารถอ่านและเข้าใจได้ง่าย ไม่ว่าจะเป็นส่วนหนึ่งส่วนใด หรือโดยรวมทั้งหมด ไม่ว่าจากใครก็ตาม (รวมถึงคนที่เขียน Code นั้นเองที่จะกลับมาอ่านมันอีกครั้งในอนาคต โดยที่จะได้ไม่ต้องเจอกับอาการที่ว่า “นี่เราเป็นคนเขียนมันเหรอ”)

คำที่ว่า “ส่วนหนึ่งส่วนใด” นั้น หมายความว่า ถ้าเราเปิดดูบาง Module หรือบาง Function ใน Code เราก็ควรที่จะสามารถเข้าใจว่ามันทำอะไร โดยที่ไม่ต้องไล่อ่าน Codebase ที่เหลือทั้งหมด มันควรเข้าใจได้ทันทีและทำตัวเหมือนเป็น Document ในตัวเอง เท่าที่สามารถจะทำได้

Code ที่ต้องอ้างอิงรายละเอียดไปถึงส่วนอื่น ๆ อยู่ตลอดเวลา จะส่งผลต่อพฤติกรรมในส่วนอื่น ๆ (ที่ดูเหมือนไม่เกี่ยวข้อง) ของ Codebase ซึ่งมันก็เหมือนกับ การอ่านหนังสือที่คุณต้องอ้างอิงถึงเชิงอรรถหรือภาคผนวกที่อยู่ช่วงท้ายประโยคอยู่ตลอดเวลา มันทำให้คุณอ่านหน้าแรกไม่จบสักที!

มาดูแนวคิดที่เกี่ยวข้องกับเรื่อง “ Readability” หรือ สามารถอ่านได้ง่าย กัน

  • Code ที่มีการ Encapsulate เป็นอย่างดี มีแนวโน้มที่จะอ่านได้ง่ายขึ้น โดยแบ่ง Code อยู่เป็นส่วน ๆ ในทุก Level
  • เกี่ยวกับการตั้งชื่อ: ตัวแปรที่มีการเลือกใช้ชื่อเป็นอย่างดี จะสามารถทำให้สามารถเข้าใจ Code ได้ง่ายและรวดเร็วขึ้น
  • บางทีความเก่ง ก็เป็นอุปสรรคได้เช่นกัน เมื่อใช้ Techniques, Paradigms หรือ Operations ที่แปลกใหม่ ก็ต้องดูด้วยว่ามันยังทำให้ Code ของคุณยังคงอ่านง่ายอยู่หรือไม่ ไม่ใช่แค่ทำให้มันสั้นลงเฉยๆ
  • ความสอดคล้อง(Consistency) เป็นสิ่งที่ดี ความสอดคล้องใน Style ทั้งในแง่ของ วิธีการวางตำแหน่งวงเล็บ และในแง่ของการ Operation ซึ่งควรช่วยในเรื่องการทำให้อ่านได้ง่ายขึ้น
  • Separation of concerns หรือ SoC พยายามทำให้แต่ละส่วนของ Codebase มีข้อมูลที่ส่งผลกระทบต่อส่วนอื่น ๆ ของ Program ให้น้อยที่สุดเท่าที่จะทำได้ สมมติว่าคุณมีระบบ People Management ที่บางครั้ง Person Object อาจมีนามสกุลที่เป็น Null ด้วย สำหรับบางคนที่เขียน Code ใน Page ที่แสดง Person Object ซึ่งอาจทำให้งานเข้าได้! Programmer อาจไม่รู้ว่า นามสกุลมีค่าเป็น Null ด้วยและอาจจะเขียน Code โดยมี Null Pointer Exception ในนั้นด้วย ในกรณีที่ Last Name เป็น Null แทนที่จะจัดการกรณีเหล่านี้ ด้วยการสร้าง API และ Contract ที่ดีสำหรับการติดต่อระหว่างส่วนต่างของ Program

บัญญัติข้อที่ 3:  Code ที่ดี ควรมี Layout และ Architecture ที่คิดออกมาอย่างดีเพื่อให้การจัดการของ State มีความชัดเจน

State ถือเป็นศัตรูสำคัญตัวหนึ่ง เพราะมันเป็นส่วนที่ซับซ้อนที่สุดของ Application ใด ๆ และจำเป็นต้องจัดการอย่างรอบคอบ ปัญหาที่พบบ่อย ได้แก่ ความไม่สอดคล้องของ Database, มีการ Update UI บางส่วนซึ่งทำให้ Data ใหม่ที่เข้ามาไม่ Update ไปยังส่วนอื่น ๆ, Operation ไม่เป็นไปตามคำสั่ง, หรือ Code ที่ซับซ้อนด้วย If Statements และ Branches ล้วนนำไปสู่การอ่านและการ Maintain ที่ยาก ควรทำให้ State อยู่บนฐานที่จะได้รับการดูแลอย่างดีเยี่ยมและมีความสอดคล้อง รวมทั้งคำนึงถึงว่า State มีการเข้าถึง, แก้ไข และทำให้ Codebase ของคุณง่ายขึ้นได้อย่างไรบ้าง บางภาษา (ตัวอย่าง Haskell) ก็มีการบังคับใช้สิ่งนี้ในระดับโปรแกรมและไวยากรณ์ด้วย แล้วคุณจะพบกับประหลาดใจว่า ความชัดเจนของ Codebase ของคุณจะดีขึ้นได้มากแค่ไหน เมื่อคุณมี Library ของ Pure Function ที่ไม่มีการเข้าถึง External State จากนั้นจะมีบางส่วนของ Stateful Code ซึ่งอ้างอิงถึง Outside Pure Functionality

บัญญัติข้อที่ 4:  Code ที่ดี ไม่ต้องสร้างเองทั้งหมด สามารถต่อยอดจากของคนอื่นได้

ก่อนที่คุณจะสร้างสิ่งที่คนอื่นสร้างไว้แล้ว ให้ลองนึกถึงปัญหาที่พบบ่อย ๆ ซึ่งคุณพยายามแก้ไขมันอยู่ หรือ Function ที่คุณกำลังพยายามทำอยู่ บางคนอาจใช้ Solution ที่เคยถูก Implement มาแล้วซึ่งคุณสามารถนำไปใช้ประโยชน์ได้ ลองใช้เวลาคิดและค้นคว้าทางเลือกต่าง ๆ หากเหมาะสมและพร้อมใช้งาน

มีการถกเถียงกันในเรื่องที่ว่า มีการพึ่งพา ที่ไม่ได้มา "ฟรี ๆ " โดยไม่มีข้อเสียตามมา การใช้ 3rd Party หรือ Open Source Library ที่เพิ่ม Function การทำงานบางอย่างเข้ามา ทำให้คุณกำลังสร้างการผูกมัดและกลายเป็นต้องพึ่งพา Library เหล่านั้น ถ้าเป็น Library ขนาดใหญ่และคุณก็ต้องการ Function การทำงานเพียงเล็กน้อย คุณต้องการที่จะรับภาระในการ Update Library ทั้งหมดหรือไม่ และยิ่งกว่านั้นหากคุณพบ Bug หรือต้องการเพิ่มประสิทธิภาพการทำงาน คุณอาจต้องพึ่งพาผู้ที่สร้างมัน (หรือผู้จัดจำหน่าย) เพื่อหาทางปรับปรุงแก้ไขมัน  หรือถ้าเป็น Open Source คุณอาจต้องสำรวจและค้นหาใน Codebase ที่คุณไม่คุ้นเคยด้วยการพยายามแก้ไขหรือปรับปรุง Function การทำงานที่มีความคลุมเครือ

แน่นอนว่า ยิ่งคุณใช้งาน Code ที่คุณต้องพึ่งพามันได้เป็นอย่างดีมากเท่าไร โอกาสที่คุณจะต้องลงทุนในการ Maintain ก็จะยิ่งน้อยลงเท่านั้น สิ่งสำคัญคือ มันคุ้มค่าไหมที่จะทำการค้นคว้าด้วยตัวเองและต้องประเมินว่าจะรวมเทคโนโลยีภายนอกเหล่านี้เข้าไปด้วยหรือไม่ รวมทั้งจะต้อง Maintain เทคโนโลยีที่จะเพิ่มลงในงานของคุณ มากน้อยแค่ไหน

ด้านล่างนี้เป็นตัวอย่างของตัวอย่างทั่วไปที่คุณไม่จำเป็นต้องทำเองหรือสร้างเองใหม่ทั้งหมดใน Project ของคุณ

  • Databases

ประเมินดูว่า คุณต้องการ CAP แบบใดสำหรับ Project ของคุณ จากนั้นเลือก Database ที่มีคุณสมบัติที่เหมาะสม Database ในที่นี้ไม่ได้หมายถึง MySQL อีกต่อไปคุณสามารถเลือกจาก:

  • “Traditional” Schema’ed SQL: Postgres / MySQL / MariaDB / MemSQL / Amazon RDS, etc.
  • Key Value Stores: Redis / Memcache / Riak
  • NoSQL: MongoDB/Cassandra
  • Hosted DBs: AWS RDS / DynamoDB / AppEngine Datastore
  • Heavy lifting: Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
  • Crazy stuff: Erlang’s Mnesia, iOS’s Core Data
  • Data Abstraction Layers

ในกรณีส่วนใหญ่ คุณไม่ควรเขียน Raw Query ไปยัง Database ใดก็ตามที่คุณจะเลือกใช้ มันมีความเป็นไปได้มากที่จะมี Library อยู่ระหว่าง DB และ Application Code ของคุณ ใช้หลักการแยก Computer Program ออกเป็น Section ย่อย ๆ ของการจัดการ Database Sessions ที่เกิดขึ้นพร้อมกัน และรายละเอียดของ Schema จาก Code หลักของคุณ อย่างน้อยที่สุด คุณก็ไม่ควรมี Raw Query หรือ SQL Inline อยู่ใน Application Code ของคุณ แต่ควร Wrap มันใน Function และรวม Function ทั้งหมดใน File ที่เรียกว่า สิ่งที่ชัดเจนจริง ๆ (เช่น “query.py”) ตัวอย่างเช่น บรรทัดที่เขียนว่า users = load_users() สามารถอ่านได้ง่ายกว่าบรรทัดที่เขียนว่า users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”) การรวมไว้ที่ศูนย์กลางแบบนี้ จะช่วยทำให้ง่ายขึ้นที่จะมีสไตล์ที่สอดคล้องกันในการ Query ของคุณ และจำกัดจำนวนของที่ที่จะไปเปลี่ยน Query ที่ไปเปลี่ยนแปลง Schema

  • Libraries และ Tools อื่น ๆ ที่อาจนำไปใช้งาน
  • Queuing หรือ Pub/Sub Services: Take your pick of AMQP providers, ZeroMQ, RabbitMQ, Amazon SQS
  • Storage: Amazon S3, Google Cloud Storage
  • Monitoring: Graphite/Hosted Graphite, AWS Cloud Watch, New Relic
  • Log Collection / Aggregation: Loggly, Splunk
  • Auto Scaling
  • Auto Scaling. Heroku, AWS Beanstalk, AppEngine, AWS Opsworks, Digital Ocean

บัญญัติข้อที่ 5:  อย่าใช้ Style ข้ามไปข้ามมาใน Codebase เดียวกัน

มีหลาย Model ที่ดีสำหรับ Programming Design, Pub/Sub, Actors, MVC เป็นต้น จงเลือกสิ่งที่คุณชอบที่สุด แล้วจงใช้ Model นั้นไปตลอดทั้ง Codebase เนื่องจาก Logic ที่แตกต่างกัน ที่ใช้จัดการกับ Data ที่แตกต่างกัน ควรใช้แยกกันในแต่ละ Codebase (อีกครั้ง นี่คือแนวคิดของ Separation Of Concerns และลดภาระกับคนที่จะมาอ่าน Code ในอนาคต)

บัญญัติข้อที่ 6:  ถ้าเป็นไปได้ ควรปล่อยให้ Computer จัดการเอง

หาก Compiler สามารถตรวจจับ Logical Errors ใน Code ของคุณและป้องกัน Behavior ที่ไม่ดี, Bugs, หรือ Crashes ทั้งหมดได้ เราก็ควรใช้ประโยชน์จากสิ่งนั้น แน่นอนว่าบางภาษามี Compiler ที่ทำให้ง่ายกว่าภาษาอื่น ตัวอย่างเช่น Haskell มี Compiler ที่ขึ้นชื่อเรื่องความเข้มงวดซึ่งส่งผลให้ Programmer ใช้ความพยายามส่วนใหญ่ในการทำ Code ให้ผ่านการ Compile เมื่อมัน Compile ผ่าน นั่นหมายถึงมัน ใช้การได้ สำหรับพวกคุณที่ไม่เคยเขียนด้วย Strongly Typed Functional Language สิ่งนี้อาจดูไร้สาระหรือเป็นไปไม่ได้ แต่คุณไม่ต้องเชื่อก็ได้ เป็นที่ยอมรับกันว่า ไม่ใช่ทุกภาษาที่มี Compiler หรือ Syntax ที่เหมาะสมที่จะใช้ Compile-Time Checking (หรือเฉพาะในบางกรณี) สำหรับผู้ที่ไม่เชื่อ ก็ให้ใช้เวลาสักครู่เพื่อค้นคว้าว่า การตรวจสอบความเข้มงวดที่เป็นทางเลือกที่คุณสามารถใช้งานมันใน Project ของคุณ และประเมินว่ามันเหมาะสมหรือไม่ นี่คือรายการบางส่วนของ Non-Comprehensive List ที่ Zachary เคยใช้เมื่อเร็ว ๆ นี้สำหรับภาษาที่มี Runtimes ที่มีความผ่อนปรน ได้แก่ :

ที่มา: https://www.toptal.com/

 

 

รับตำแหน่งงานไอทีใหม่ๆ ด้วยบริการ IT Job Alert

 

อัพเดทบทความจากคนวงในสายไอทีทาง LINE ก่อนใคร
อย่าลืมแอดไลน์ @techstarth เป็นเพื่อนนะคะ

เพิ่มเพื่อน

 

บทความล่าสุด