2 คะแนน โดย GN⁺ 2024-08-06 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ประสบการณ์การเขียนแกนหลักของโปรแกรมที่ใช้งานและปรับแก้เองมาตลอด 2 ปีใหม่อีกครั้งภายในหนึ่งเดือน ทำให้ความเชื่อเดิมเกี่ยวกับ การทดสอบและการจัดการเวอร์ชัน สั่นคลอน
  • ในปี 2015 เคยมองว่า การทดสอบและเวอร์ชัน คือหัวใจของซอฟต์แวร์ที่อยู่ได้นาน มากกว่านามธรรมที่แย่ แต่เมื่อผ่าน Mu และ Freewheeling Apps วิธีทำงานจริงก็ค่อย ๆ เปลี่ยนไป
  • มองว่าโปรแกรมที่อยู่ได้นานไม่ควรมุ่งทำเพื่อคนจำนวนมาก แต่ควรสร้างภายในขอบเขตของคน บริบท และฟังก์ชันที่รู้จักดี และยอมรับข้อจำกัดตามจริงอย่าง Dunbar's number
  • ไทป์ นามธรรม การทดสอบ เวอร์ชัน state machine, immutability และ formal analysis มีประโยชน์ในพื้นที่ที่ไม่คุ้นเคย แต่ถ้าใช้มากเกินไปจะกลายเป็น หนี้ทางเทคนิค ที่บดบังความซับซ้อนที่ไม่จำเป็น
  • เมื่อความเข้าใจบริบทเริ่มนิ่งแล้ว การทิ้งส่วนใหญ่แล้วสร้างใหม่มีคุณค่า และควรยกสถานการณ์ที่จำเป็นทั้งหมดขึ้นมาไว้ในหัวพร้อมกัน แล้ว ประกอบภาพรวมทั้งหมดในครั้งเดียว

การเปลี่ยนมุมมองต่อการทดสอบและการจัดการเวอร์ชัน

  • ผมจัดการกับปัญหาการเลือกและสร้างโปรแกรมที่พึ่งพาได้ในระยะยาวมาโดยตลอด แต่ก็ไม่ได้รู้สึกว่าตัวเองทำเรื่องนี้ได้ดีนัก
  • ในช่วงหนึ่งเดือนที่ผ่านมา ผมได้ เขียนแกนหลักใหม่ ของโปรแกรมที่ใช้งานและค่อย ๆ ปรับแก้มาตลอด 2 ปี
    • หลังจากนั้นอีกหลายวันเป็นช่วงเวลาที่ผมเรียบเรียงว่าได้เรียนรู้อะไร และต่อไปจะไปทางไหน
    • งานครั้งนี้ทำให้เริ่มเห็นการเปลี่ยนแปลงในระดับชีวิตที่กว้างขึ้น
  • ในปี 2015 ผมเคลือบแคลงต่อ abstraction และให้ความสำคัญกับการทดสอบกับการจัดการเวอร์ชัน
    • ผมมองว่าในโค้ดมี abstraction ที่แย่อยู่มาก และการทดสอบกับเวอร์ชันคือความก้าวหน้าสำคัญของยุค 2000
    • ผมหาสาเหตุของปัญหาจากแรงจูงใจที่ไม่ดี abstraction ที่มากเกินไป และการทดสอบกับเวอร์ชันที่ไม่เพียงพอ
    • Mu1 เป็นความพยายามออกแบบแพลตฟอร์มที่ใช้การทดสอบและ layers เป็นข้อจำกัดพื้นฐาน
  • ในปี 2017 ผมเริ่มทำ Mu1 ใหม่เป็น Mu ในปัจจุบัน
    • ช่วงแรก ใช้ไอเดียใหม่ทั้งหมดเกี่ยวกับการทดสอบและเลเยอร์
    • เมื่อเวลาผ่านไป ผมใช้งานไอเดียเหล่านั้นน้อยลง
    • ปัจจุบัน Mu มีการทดสอบจำนวนมาก แต่ส่วนใหญ่เป็น การทดสอบทั่วไป และไม่สามารถย้ายโครงสร้างพื้นฐานของเลเยอร์มาได้
  • ในปี 2022 ผมเริ่มสร้าง Freewheeling Apps
    • ตอนแรกไม่มีการทดสอบ และภายหลังจึงเขียนการทดสอบอย่างละเอียดให้กับ text editor ซึ่งเป็นชิ้นส่วนแกนหลัก
    • วิธีทดสอบส่วนที่เหลือหาได้ยาก แต่ก็เดินหน้าต่อได้เพียงพอแม้ไม่มีการทดสอบ
  • ในปี 2024 ผมลบการทดสอบทั้งหมด
    • ผมเริ่มสร้าง text editor ใหม่ครั้งใหญ่ และวิธีนี้อาจทำให้กังวลเรื่อง merge conflict กับ Freewheeling Apps อื่น ๆ
    • ผลก็คือความคิดเรื่องการจัดการเวอร์ชันก็หยุดลงด้วย
    • หลังจากละทิ้งการทดสอบและเวอร์ชันแล้วกลับได้โปรแกรมที่ดีขึ้น ทำให้ยากจะเพิกเฉยต่อ cognitive dissonance กับความเชื่อเดิมได้อีกต่อไป

ข้อสรุปปัจจุบันสำหรับโปรแกรมที่อยู่ได้นาน

  • การสร้างสิ่งที่จะอยู่ได้นานสำหรับคนจำนวนมากนั้นยากเกินไป จึงมองว่าดีกว่าที่จะไม่พยายามทำแบบนั้นตั้งแต่แรก
    • ควรถูกกำกับด้วยสิ่งที่รู้จักดี คนที่รู้จักดี และ Dunbar's number
  • ผมมองว่าซอฟต์แวร์ส่วนใหญ่ในโลกติดเชื้อแรงจูงใจที่จะรับใช้คนจำนวนมากในระยะสั้น
    • ผมพยายามโฟกัสซอฟต์แวร์ที่มีโลโก้บนเว็บไซต์ไม่มากเท่าที่เป็นไปได้
    • ชอบซอฟต์แวร์ที่สร้างง่าย มี dependency น้อย และไม่อัปเดตอัตโนมัติ
    • เมื่อกรองด้วยข้อจำกัดเหล่านี้ ปริมาณ ซอฟต์แวร์ที่อยู่ได้นาน ที่มนุษยชาติสร้างมาจนถึงตอนนี้มีน้อยมาก
  • การเปลี่ยนแปลงเล็กน้อยของบริบท เช่น คน สถานที่ และฟังก์ชันที่ต้องการรองรับ สามารถเปลี่ยนได้มากว่าโปรแกรมเข้ากับบริบทนั้นได้ดีเพียงใด
    • ในสภาพแวดล้อมที่ถูกครอบงำด้วย short-termism การเตรียมรับมือกับข้อเท็จจริงนี้ทำได้ยาก
  • เพราะปริมาณงานในอดีตมีน้อย และขอบเขตการใช้งานต่อโปรแกรมก็ต่ำ โปรแกรมที่ตัดสินใจสร้างใหม่จึงมีแนวโน้มจะเข้าไปสู่พื้นที่ที่ไม่รู้จักในทางใดทางหนึ่ง
    • แม้แต่ตอนพยายามใส่ “drawing lines” แบบพิเศษเข้าไปใน text editor ก็เกิดคำถามหลายข้อ
      • เคอร์เซอร์สามารถอยู่บนรูปวาดได้หรือไม่
      • สามารถวาดรูปบนบรรทัดหนึ่งในขณะที่เคอร์เซอร์อยู่ที่อีกบรรทัดได้หรือไม่
      • รูปวาดสูงกว่าบรรทัดข้อความ แล้วจะมองเห็นเพียงบางส่วนจากด้านบนของหน้าจอได้หรือไม่
      • สามารถวาดบนรูปวาดที่มองเห็นเพียงบางส่วนได้หรือไม่
    • คำตอบต่อคำถามเหล่านี้ไม่ใช่คำตอบที่เหมาะสมที่สุดอยู่นาน ทำให้การแก้ปัญหาเฉพาะหน้าซ้อนทับบนการแก้ปัญหาเฉพาะหน้าอีกชั้น

เครื่องมือจำเป็น แต่ถ้ามากเกินไปจะกลายเป็นหนี้ทางเทคนิค

  • ไทป์ abstraction การทดสอบ เวอร์ชัน state machine, immutability และ formal analysis เป็น เครื่องมือ ที่ใช้ได้ในภูมิประเทศที่ไม่คุ้นเคย
    • ใช้ตามรสนิยมเท่าที่จำเป็นก็พอ
  • คนเรามักใช้เครื่องมือที่ตนเองชอบมากเกินไปได้ง่าย
    • ผมมองว่าปริมาณการใช้งานที่เหมาะสมของเครื่องมือเหล่านี้มีน้อยมาก
    • ต้องน้อยกว่าความรู้สึกที่เรียนรู้มาในสภาพแวดล้อมที่ถูกครอบงำด้วย short-termism อย่างมาก
  • เมื่อใช้เครื่องมือเกินจำเป็น จะกลายเป็น หนี้ทางเทคนิค
    • ทำให้สังเกตได้ยากว่าโปรแกรมซับซ้อนโดยไม่จำเป็น
    • ทำให้โปรแกรมอยู่ได้น้อยกว่าสภาพที่ควรจะอยู่ได้นานกว่านี้
    • ทำให้แก้โปรแกรมได้ยากขึ้นเมื่อบริบทเปลี่ยนไป

การเขียนใหม่และ “สร้างทั้งหมดในครั้งเดียว”

  • เมื่อความเข้าใจต่อบริบทเริ่มมั่นคงแล้ว การทิ้งส่วนใหญ่ของโปรแกรมแล้วเริ่มใหม่ตั้งแต่ต้นมีคุณค่า
  • ก่อนเขียนใหม่ ต้องนำทุกสิ่งที่ต้องการจากโปรแกรมและทุกสถานการณ์ที่ต้องรองรับเข้ามาไว้ในหัวพร้อมกัน
    • กระบวนการนี้ยาก แต่เป้าหมายคือการไปถึงสภาพที่สามารถสร้างทุกอย่างได้ในครั้งเดียว
  • วิธีสุดท้ายคือ การสร้างทุกอย่างในครั้งเดียว
  • ในประสบการณ์ครั้งนี้ การทดสอบและเวอร์ชันกลับกลายเป็นอุปสรรคต่อการไปถึงปลายทางของวิวัฒนาการนี้
    • การทดสอบทำให้ลืมปัญหาที่ควรกังวล
    • การจัดการเวอร์ชันทำให้ยึดติดกับอดีตต่อไป
    • ทั้งสองอย่างให้ผลย้อนกลับ และการวางมันลงต้องอาศัยการเปลี่ยนทิศทางครั้งใหญ่
  • ผมมองว่าซอฟต์แวร์ทั้งหมดที่สร้างมาจนถึงตอนนี้ และ Freewheeling Apps อยู่ที่ขั้นที่ 6 ของเส้นทางนี้

ขีดจำกัดของความซับซ้อนและ data-oriented design

  • หากโปรแกรมซับซ้อนเกินไป อาจเป็นไปไม่ได้ที่จะยกมันขึ้นมาไว้ในหัวทั้งหมดในขั้นที่ 8
    • ผมมองว่าซอฟต์แวร์ส่วนใหญ่จนถึงตอนนี้ โดยเฉพาะซอฟต์แวร์ที่เขียนโดยคนมากกว่าสักสองคน เข้าข่ายนี้
    • แม้แต่ text editor ขนาดเล็กก็ยังเป็นภาระหนัก จนผมใช้เวลาส่วนใหญ่ของเดือนนั้นเตรียมตัวรับมือกับความกลัวนั้น
  • ไม่ใช่ว่าซอฟต์แวร์ทุกชิ้นจำเป็นต้องไปถึงขั้นที่ 9
    • Freewheeling Apps จำนวนมากเรียบง่ายพอและวิวัฒนาการอย่างช้า ๆ
    • ผมมองว่าเพียงมีคนจำนวนน้อยใช้งาน ก็สามารถทำให้เสถียรจนไม่มีบั๊กได้ โดยไม่ขึ้นกับตัวเลือกการออกแบบในช่วงต้น
    • โดยเฉพาะตอนนี้ที่รู้วิธีทำให้ชิ้นส่วนซับซ้อนชิ้นหนึ่งของแกนหลักเรียบง่ายลงแล้ว
  • ถึงอย่างนั้น หากเกิดคุณค่า การรู้ว่าจะปรับปรุงอย่างไรก็เป็นเรื่องดี
  • วิธีที่ดูเหมือนมีประโยชน์อย่างชัดเจนในการไปถึงขั้นที่ 9 คือ data-oriented design
    • นี่ไม่ใช่เครื่องมือที่นำไปใช้แบบสุ่มสี่สุ่มห้าได้ แต่เป็นวิธีคิดที่มองภาพใหญ่ของการที่โปรแกรมเข้าถึงข้อมูล
    • ต้องไม่ให้เครื่องมืออย่าง ECS บดบังกิจกรรมทางปัญญาที่เป็นแก่นแท้
  • การแบ่งขั้นตอนนี้อาจไม่ได้ถูกต้องทั้งหมด
    • ผมอาจประเมินเครื่องมือที่มีประสบการณ์น้อยต่ำเกินไป
    • สิ่งที่อยู่เลยขั้นเหล่านี้ไปก็ยังคงเป็นคำถามเปิด
  • สามารถเห็นร่องรอยความคิดที่เปลี่ยนไปตั้งแต่ บทความเกี่ยวกับวิธีเขียนโปรแกรม ที่เขียนไว้ในปี 2019

1 ความคิดเห็น

 
GN⁺ 2024-08-06
ความคิดเห็นจาก Hacker News
  • ถ้าไม่มีเทสต์ ก็จะไม่เห็นว่า เทสต์ล้มเหลว ปัญหาจึงแค่ดูเหมือนหายไปเท่านั้น
    ผมไม่เคยทดสอบอะไรแล้วไม่เจอบั๊กเลย และสิ่งที่ทดสอบส่วนใหญ่ก็เป็นสิ่งที่ผมคิดอยู่แล้วว่าพร้อมปล่อยได้
    ถ้าลบเทสต์ทิ้ง สุดท้ายคนที่มีแนวโน้มจะถูกหลอกก็คือตัวเองเท่านั้น อ่านบทความแล้วดูเหมือนว่าเขาจะเหนื่อยกับ การจัดการตัวแปร/การตั้งค่า มากกว่าตัวเทสต์เอง ซึ่งก็เข้าใจได้เต็มที่ แต่ต้องมีจำนวนผู้ใช้ก่อนถึงจะทำเงินได้ และถ้าเป็นปัญหาง่าย ๆ ตลาดก็คงอิ่มตัวด้วยโซลูชันครอบจักรวาลไปแล้ว

    • ผมมองว่าเรื่องนี้ ขึ้นกับโดเมน บางส่วนของโค้ดเบสที่กำลังทำอยู่ตอนนี้ เทสต์ช่วยรีแฟกเตอร์ได้มาก แต่บางส่วนมีพฤติกรรม UI เยอะ การทดสอบด้วยมือจึงเร็วกว่ามาก
      ถ้า UI หรือเวิร์กโฟลว์เปลี่ยนเร็วเกินไป เราจะไม่เขียนเทสต์ เพราะรู้อยู่แล้วว่ารอบถัดไปมันจะไร้ประโยชน์ แต่ถ้าเปลี่ยนช้าเกินไป ส่วนนั้นก็มักไม่ต้องกลับไปแตะอีก โอกาสที่การรีแฟกเตอร์จะใส่บั๊กใหม่เข้าไปจึงน้อย เทสต์หรือไทป์ไม่ใช่จอกศักดิ์สิทธิ์ที่แก้ได้ทุกอย่าง แต่เป็นเครื่องมือที่ต้องใช้ให้เหมาะกับงาน ผมไม่เคยเห็นโค้ดเบสที่แม้มี test coverage ดีแล้ว แต่ไม่พบบั๊กจากการทดสอบด้วยมือหรือการใช้งานจริงเลย พูดแบบเกินจริงนิดหน่อยคือ ถ้าเก่งพอจะเขียนเทสต์ที่สมบูรณ์แบบได้ ก็เขียนโค้ดที่สมบูรณ์แบบไปเลยก็ได้ ถ้าเขียนเทสต์ให้สมบูรณ์แบบไม่ได้ แล้วจะรู้ได้อย่างไรว่าเทสต์นั้นครบถ้วน ไม่มีบั๊ก และมีประโยชน์จริง
    • คำพูดที่ว่า “เทสต์แสดงให้เห็นได้ว่ามีบั๊กอยู่ แต่ แสดงให้เห็นไม่ได้ว่าไม่มีบั๊ก” ตรงกับประสบการณ์ของผมมากกว่า
      ทุก ๆ ไม่กี่เดือนผมจะเจอบั๊กใหม่และตั้งใจเพิ่มเทสต์เข้าไป แต่ไม่กี่เดือนต่อมา คนที่เพิ่งลองใช้ 10 นาทีแรกก็ยังเจอบั๊กใหม่อีกอยู่ดี เวอร์ชันใหม่ก็คงยังมีบั๊กให้ค้นพบอยู่ แต่ด้วยโครงสร้างข้อมูลที่เลือกใช้ ผมคิดว่าเทสต์เก่าจำนวนมากไม่จำเป็นในเชิงโครงสร้างแล้ว อย่างน้อยสำหรับการใช้งานเบา ๆ ผมหวังว่าถ้าจับบั๊กเพิ่มได้อีกไม่กี่ตัว ก็น่าจะเสถียรพอสมควร เทสต์มีค่ามากเมื่อทีมใหญ่ต้องเปลี่ยนโค้ดเบสไปเรื่อย ๆ แต่กรณีนี้กำลังพยายามสร้างบางอย่างที่มี ชุดฟีเจอร์คงที่
    • ผมดูวิดีโอ Go Testing By Example ของ Russ Cox ที่มีคนแนะนำในเธรดล่าสุด: https://www.youtube.com/watch?v=X4rxi9jStLo
      มีคำแนะนำที่มีประโยชน์มากมาย แต่สิ่งที่อยากพูดถึงเป็นพิเศษคือการทดสอบโดยเทียบกับการทำงานที่เรียบง่ายกว่า เช่น implementation แบบ brute force ได้ ตรงนี้มีปัญญาที่ลึกกว่านั้นอยู่ ประโยชน์ของเทสต์ขึ้นอยู่กับว่า implementation ของเทสต์นั้นเรียบง่ายกว่า implementation ของสิ่งที่ถูกทดสอบมากแค่ไหน พูดให้หนักกว่านั้นคือ เทสต์จะมีประโยชน์ก็ต่อเมื่อมันเรียบง่ายกว่าสิ่งที่ถูกทดสอบเท่านั้น ต่อให้เขียนเทสต์มากแค่ไหน สุดท้ายก็ยังต้องใช้เหตุผลกับโค้ดอยู่ดี และการที่อะไรบางอย่างถูกเรียกว่า “เทสต์” ไม่ได้ทำให้มันมีประโยชน์ขึ้นมาเอง นั่นจึงเป็นเหตุผลที่ผมคิดว่าโปรแกรมเมอร์จำนวนมากระแวงการแตกฟังก์ชันออกเป็นชิ้น ๆ ที่ไม่ใช่อินเทอร์เฟซที่มีประโยชน์เพียงเพื่อให้เขียนเทสต์ง่ายขึ้น, การเทสต์ helper ง่าย ๆ หรือ query เล็ก ๆ เพียงเพื่อ coverage, หรือการนำ dependency inversion และ mocking เข้ามาเพื่อเทสต์เท่านั้น แน่นอนว่าแต่ละอย่างอาจมีเหตุผลของมัน แต่สิ่งสำคัญคือต้องไม่หลุดจากแก่นหลัก
    • สำหรับผม ทุกครั้งที่เขียน unit test บั๊กก็โผล่ออกมา
      ปกติผมไม่ค่อยใช้แนวทาง test-driven development ที่เขียนเทสต์ให้ล้มเหลวก่อน แต่บางครั้งก็ทำ ดังนั้นเทสต์แบบนี้มักเขียนกับโค้ดที่ผมคิดว่าใช้งานได้อยู่แล้ว อย่างไรก็ตาม โดยทั่วไปผมชอบ test harness มากกว่า unit test[0] มันยังช่วยหาบั๊กได้ แต่กระแสการทำงานไม่เป็นเส้นตรงเท่า มันทำให้ผมทดสอบระหว่างพัฒนาได้มากขึ้น และแก้บั๊กตรงนั้นได้เลย
      [0] https://littlegreenviper.com/testing-harness-vs-unit/
    • การให้ความสำคัญกับ unit/integration test แบบอัตโนมัติเป็นกระแสที่ค่อนข้างสมัยใหม่ น่าจะเริ่มราวปลายยุค 90 ก่อนหน้านั้นก็มีซอฟต์แวร์ขนาดค่อนข้างใหญ่และเสถียรมากถูกปล่อยออกมา
      เช่น Linux kernel เมื่อก่อนมีเทสต์ไม่มาก และทุกวันนี้ดูเหมือนจะมีมากขึ้น Unix ก็คงไม่ได้มี “เทสต์” มากนัก คอมไพเลอร์มักมีเทสต์อยู่บ้าง แต่ระบบปฏิบัติการมีน้อยกว่า และเกมอย่าง Doom ก็มีแนวโน้มว่าไม่ได้มีเทสต์มากนัก ท้ายที่สุดต้องหา จุดสมดุล ให้เจอ เรารู้ว่า automated testing ไม่ว่าจะเป็น unit, integration หรือ end-to-end test ช่วยสร้างซอฟต์แวร์คุณภาพดีได้ ขณะเดียวกัน เทสต์ที่ดีก็ไม่ได้เขียนง่ายเสมอไป เทสต์ที่แย่ทำให้การรีแฟกเตอร์ยากขึ้น และเทสต์ที่ไม่นิ่งก็กินเวลามากในโปรเจกต์ใหญ่ ถึงอย่างนั้น โดยเฉพาะถ้าพัฒนาคนเดียว การลองหลาย ๆ วิธีแล้วค้นหาสิ่งที่เหมาะกับตัวเองก็เป็นเรื่องน่าสนใจ
  • ส่วนที่ว่า “พอเลิกยึดติดกับการทดสอบและเวอร์ชัน โปรแกรมก็ดีขึ้นมาก” นั้นเข้าใจได้ยาก ไม่รู้ว่าในปี 2024 จะมีใครอยากเขียนโปรแกรมโดยสมัครใจโดยไม่มี การจัดการซอร์สโค้ด
    แม้จะเป็นโปรเจกต์คนเดียว ความสามารถในการทำงานข้ามหลายอุปกรณ์ ดูประวัติ ย้อนกลับ และใช้ branch ก็ให้คุณค่ามหาศาลโดยแทบไม่มีต้นทุน บางทีผมอาจเข้าใจผิดว่าผู้เขียนหมายถึงอะไรด้วยคำว่า “เวอร์ชัน”

    • เขากำลังพยายามสร้างบางสิ่งที่เล็ก รวดเร็ว และ มีชุดฟีเจอร์คงที่ มีการตัดสินใจว่าจะสร้างบนฐานที่ไม่เปลี่ยนบ่อย และมีเบื้องหลังเพิ่มเติมที่ https://akkartik.name/freewheeling
      ถูกแล้วที่แนวทางนี้ไม่เหมาะกับโปรแกรมส่วนใหญ่ที่ผู้คนสร้างกันในวันนี้ กล่าวคือทีมใหญ่และความต้องการที่เปลี่ยนไปเรื่อย ๆ ถึงอย่างนั้นก็ยังใช้การจัดการซอร์สโค้ดอยู่ ตามที่ต้นฉบับบอกไว้ เพียงแค่เลิกกังวลเรื่องการเกิด merge conflict กับ fork อื่น ๆ เท่านั้น ตอนนี้มี fork มากกว่า 24 รายการแล้ว รายละเอียดอยู่ในลิงก์ด้านบน สำหรับการใช้งานพื้นฐานอย่างการสำรองข้อมูล, “เมื่อกี้เปลี่ยนอะไรไปนะ?”, และการเอาซอฟต์แวร์ไปลงเครื่องใหม่ ก็ใช้ version control อยู่ เพียงแต่สำหรับโปรแกรมนี้โดยเฉพาะ เขาเลิกมอง version control เป็น เครื่องมือสำหรับทำความเข้าใจและติดตาม ว่าอะไรเปลี่ยนไปแล้ว รายละเอียดเพิ่มเติมอยู่ที่ https://akkartik.name/post/wart-layers เช่น เริ่มใส่ใจเรื่องความเรียบร้อยของ commit message น้อยลง version control ยังมีอยู่ แต่ในบริบทแคบ ๆ แบบนี้ที่ต้องการทำให้เป็นผลงานที่ทนทาน มีชุดฟีเจอร์คงที่ และอยู่ได้หลายสิบปี มันจึงมีลำดับความสำคัญต่ำลงในฐานะ “แนวปฏิบัติการเขียนโปรแกรมที่ดี”
    • ดูเหมือนผู้เขียนไม่ได้อยู่ในสถานการณ์ที่ต้องรองรับผู้ใช้มืออาชีพหรือผู้ใช้แบบจ่ายเงิน และดูเหมือนต้องการ อิสระในการทดลอง มากกว่าการรับประกันเวอร์ชันปกติที่รู้ว่าดี
      ดูเหมือนไม่ใช่สถานการณ์ที่ต้องจัดการระบบขนาดใหญ่หรือทำงานทีมที่สำคัญด้วย ภายใต้เงื่อนไขแบบนี้ เครื่องมือต่าง ๆ อาจไม่ได้ให้คุณค่ามากนัก นักฟลูตในวงออร์เคสตราขนาดใหญ่ที่เล่นซิมโฟนีซับซ้อนต้องการโน้ตเพลงและวาทยกร แต่ถ้าเล่นคนเดียวไปกับ drum machine หรือเล่นฟรีแจ๊ส โน้ตเพลงก็แทบไม่จำเป็น และอาจเป็นอุปสรรคเสียด้วยซ้ำ
    • ผู้เขียนดูเหมือนกำลังเผชิญ ความเหนื่อยล้าทางจิตใจ หรือ burnout จากการเขียนโปรแกรม ถ้า version control รบกวนจิตใจถึงขนาดนั้น ผมคิดว่านั่นเป็นสัญญาณค่อนข้างดีว่าควรพัก
    • โปรแกรมเมอร์ถูกตัวเลือกและออปชันท่วมท้นอยู่ตลอด เครื่องมือและจิตวิญญาณแห่งยุคที่บอกว่า “เครื่องมือที่ดีที่สุด” โดยทั่วไปมักไหลไปในทิศทางที่ทำให้บางงานง่ายขึ้น
      แต่ถ้ามีตัวเลือกง่าย ๆ 1000 อย่างอยู่ตลอด การเลือกตัวเลือกที่ถูกต้องจะสร้าง ภาระทางความคิด อย่างมาก นั่นก็เป็นหนึ่งในเหตุผลที่อุตสาหกรรมยกย่องสารพัด best practice จนแทบศักดิ์สิทธิ์ และกดดันทางสังคมต่อคนที่ไม่ทำตาม สถาปัตยกรรมที่แย่และโค้ดสปาเกตตีอันเลวร้ายนั้นทำงานด้วยยากมาก แต่หากตั้งคำถามกับสิ่งที่ดูเหมือนถูกต้องโดยปริยาย และสำรวจสภาพแวดล้อมการพัฒนาที่เข้มงวดซึ่งลดตัวเลือกกับเครื่องมือลง ก็อาจช่วยให้โฟกัสกับปัญหาสุดท้ายได้มากขึ้น version control เองก็ชักนำให้ branch แบ่งโปรแกรมออกเป็น “ฟีเจอร์อิสระ” ประวัติก็ทำให้เราใช้หน่วยฟีเจอร์ที่อาจล้าสมัยไปแล้วอย่างไม่ตั้งคำถาม และการทำงานร่วมกันมักทำให้ขอบเขตองค์กรที่ไม่เกี่ยวกันถูกตรึงลงไปในสถาปัตยกรรมโค้ด เรื่องนี้สอดคล้องกับที่ Mel Conway เคยพูดไว้ด้วย ประโยชน์ของ version control เป็นสามัญสำนึก แต่ในระดับ “แก้ปัญหาธุรกิจ X” ก็มีการแลกเปลี่ยนจริงอยู่ การที่แทบมองไม่เห็นการแลกเปลี่ยนแบบนี้ในระดับอุตสาหกรรมเป็นเรื่องที่ชวนคิด
    • ในกรณีนี้ สิ่งที่ผู้เขียนพูดน่าจะหมายถึงการเขียน ตรรกะเรื่องเวอร์ชัน ลงไปในตัวแอปเอง เช่น API endpoint แยกตามเวอร์ชันเพื่อรองรับ backward compatibility
  • ตอนแรกคิดว่าผู้เขียนผิดไปหมด แต่ก็ยังมี insight ดี ๆ อยู่บ้าง
    workflow นี้เข้ากับผู้เขียนได้ดีมาก พวกเราส่วนใหญ่ก็นึกถึงช่วงเวลาที่เคยหงุดหงิดหรือ productivity ลดลงเพราะ Git หรือ automated test ได้เหมือนกัน มีทางออกที่เรียบง่ายกว่าและไม่รบกวนเท่า เช่น สำรองโค้ดด้วย Dropbox, FTP เป็นต้น เหตุผลที่แนวทางข้างต้นเหมาะดีคือผู้เขียนกำลัง optimize productivity ของตัวเองใน โปรเจกต์ส่วนตัวที่ทำด้วยใจรัก และร่วมงานกับคนจำนวนน้อย automated test มีประโยชน์ แต่ดูเหมือนผู้เขียนชอบทำโปรแกรมที่เล็กพอจนคุณค่าของมันแสดงออกมาได้ยาก ผมยังมองว่า automated test มีคุณค่าแม้ในบริบทนี้ แต่ทุกคนน่าจะเห็นตรงกันได้ว่า automated test ทำให้ช้าลง แน่นอนว่าหลายคนจะโต้แย้งว่ามันให้ผลตอบแทนในภายหลัง version control และ automated test แก้ปัญหาจริง การเริ่มโปรเจกต์โดยไม่มี version control ในยุคนี้ไม่สมเหตุสมผล และที่ automated test เป็น best practice ก็มีเหตุผลของมัน แต่สำหรับ use case เฉพาะของผู้เขียน ฟังดูมีเหตุผล ถ้าตัดส่วน version control/test ที่เป็นประเด็นถกเถียงออกไป ข้อ 7/8/9 ก็จับวิธีคิดของผมเวลาจะเขียนและ refactor โปรแกรมใหญ่ ๆ ได้อย่างสมบูรณ์แบบ เขียน ทิ้ง แล้วเขียนใหม่

    • ไม่เห็นด้วยเรื่อง version control แม้จะเป็นโปรเจกต์คนเดียวและไม่มี branch หลายเวอร์ชันก็ตาม
      คนเราผิดพลาดได้ และในโปรเจกต์ที่เกิน 100,000 บรรทัด การรู้ว่า 3 สัปดาห์ที่ผ่านมาเปลี่ยนอะไรไปบ้างช่วยได้มาก มีประโยชน์ต่อการหาปัญหาและแก้ไข ฟีเจอร์ที่ดีกว่านั้นคือ branch ที่ให้ลองทำสิ่งที่อยากทำได้ โดยยังรักษาวิธีกลับไปยังสถานะเสถียรก่อนหน้าไว้ ผมคิดว่าไม่มี automated test ก็ยังพอได้
    • แม้จะเป็นโปรเจกต์เดี่ยว เวลาที่ใช้เรียน Git ให้พอตั้งค่า .gitignore แล้วรัน git init, git add -A, git commit -a -m "before I changed the foo function to use bar" เพื่อกลับไปยัง revision ก่อนหน้าได้ ก็คุ้มค่ามาก
      ไม่จำเป็นต้องเชี่ยวชาญ Git แต่แค่มี commit message และเวอร์ชันให้ย้อนกลับ ก็ช่วยชีวิตมานับครั้งไม่ถ้วนแล้ว ยังไม่ต้องพูดถึงฟีเจอร์ขั้นสูงกว่านั้น
  • เป็น บทความที่ค่อนข้างสับสน จริง ๆ อยากรู้มากว่าอะไรทำให้มันขึ้นไปถึงอันดับ 1 ได้

    • มองด้านหนึ่ง อาจเป็นบทความของนักพัฒนาที่ทดลองเครื่องมือและเทคนิคต่าง ๆ เพื่อพัฒนาชีวิตของตัวเอง อีกด้านหนึ่งก็อาจเป็น เหยื่อล่อให้คนถกเถียง ก็ได้
  • แรงจูงใจหลักในการมีชุดทดสอบที่สมเหตุสมผลคือ การลดความหงุดหงิด ชุดทดสอบช่วยให้นักพัฒนามีความมั่นใจในการพัฒนาระบบต่อไป
    ถ้าทำออกมาดี ก็มักจะเกิดความคิดทำนองว่า “ส่วนที่เหลือหาวิธีทดสอบได้ยาก แต่ยังไงก็ไปต่อได้โอเค” อยู่บ่อย ๆ เมื่อความซับซ้อนของฟีเจอร์เพิ่มขึ้น ความยากในการทดสอบคอมโพเนนต์หรือทั้งระบบอาจสูงจนรับมือได้ยาก แต่ปรัชญาที่ว่าการเลิกใช้การทดสอบและการจัดการเวอร์ชันทำให้ได้โปรแกรมที่ดีกว่านั้นไม่สามารถขยายเกินคนคนเดียวได้ และเป็นไปได้ก็ต่อเมื่อคนนั้นรู้การตัดสินใจทั้งหมดทั้งปัจจุบันและอดีตที่ฝังอยู่ในซอร์สโค้ดเหมือนเป็นความทรงจำใกล้ชิดเมื่อไม่นานมานี้เท่านั้น ยิ่งไปกว่านั้น ถ้ารู้รายละเอียดการอิมพลีเมนต์ลึกมาก การตรวจสอบความเปลี่ยนแปลงทั้งหมดก็ต้อง ทำด้วยมือ ตามนิยาม

    • ก่อนหน้านี้ผมเคยเห็นเรื่องของคนคนหนึ่งใน HN ที่จะไม่ merge โค้ดใด ๆ เลย ถ้าไม่ใช่โค้ดที่เขาเขียนเองในวันนั้น
      เรื่องคือถ้าถึงปลายวันแล้วยังไม่อยู่ในสภาพที่ merge ได้ นั่นแปลว่าเขายังเข้าใจปัญหาไม่พอที่จะถ่ายทอดมันออกมาได้ภายในหนึ่งวัน จึงค่อยเริ่มลองใหม่ในเช้าวันถัดไป ไม่แน่ใจว่ามีใครจำเรื่องนี้ได้ไหม หรือว่าผมสับสนกับเว็บอื่นหรือเกร็ดเล่าเรื่องอื่นอยู่
    • ในฐานะทีมโปรแกรมมิงคนเดียว เรื่องนี้ก็ฟังขึ้น พูดตรง ๆ แม้จะทำงานคนเดียว แค่คิดว่าจะเขียนโปรแกรมโดยไม่มี ชุดทดสอบ หรือการจัดการเวอร์ชันก็ยังน่ากลัว
      เอกสาร การทดสอบ และการจัดการเวอร์ชันช่วยลดปริมาณบริบทของโค้ดที่ผมต้องจำ รายละเอียดของโค้ดตรงหน้าก็ยังต้องจำอยู่ แต่ถ้าได้ทำเอกสาร ทดสอบ และเช็กอินว่าทำไม/เปลี่ยนอย่างไรพร้อมข้อความ commit ที่ดีแล้ว ผมก็เอาโค้ดนั้นออกจากหัวและไปทำงานถัดไปได้
  • ตัวอย่างที่ดีของข้อ 3 ที่ว่า “การเปลี่ยนแปลงเล็ก ๆ ในบริบท เช่น คน/สถานที่/ฟีเจอร์ที่ต้องการรองรับ ทำให้ความเหมาะสมของโปรแกรมกับบริบทนั้นเปลี่ยนไปอย่างรวดเร็ว” คือ K9 Mail ซึ่งตอนนี้กำลังกลายเป็น Thunderbird เวอร์ชัน Android
    K9 Mail เริ่มจาก UI ที่ไม่ค่อยเป็นแบบดั้งเดิม โดยแสดงรายการบัญชีอีเมลบนหน้าจอหลัก และแสดงจำนวนข้อความที่ยังไม่ได้อ่านกับจำนวนข้อความทั้งหมดของแต่ละบัญชี แม้จะมี inbox รวม แต่ก็ไม่ได้บังคับให้ผู้ใช้ใช้ ผมจำได้ว่าเลือกแอปนี้อย่างชัดเจนเพราะอยากแยกบัญชีส่วนตัวหนึ่งบัญชี บัญชีงานหนึ่งบัญชี และบัญชีงานหลายบัญชีที่ลูกค้าให้มาไว้ต่างหาก ผู้ใช้ K9 จำนวนมากก็น่าจะเลือกแอปนี้ด้วยเหตุผลเดียวกัน นั่นเป็นเหตุผลที่มีเสียงไม่พอใจมากเมื่อผู้พัฒนาย้ายไปใช้ UI แบบ Android ดั้งเดิมที่รายการบัญชีเลื่อนออกมาจากด้านซ้าย และต้องแตะเพิ่มอีกหนึ่งครั้งเพื่อสลับบัญชี ถ้าเราชอบ UI แบบนั้น เราก็คงไม่น่าเลือก K9 ตั้งแต่แรก ดังนั้นการเปลี่ยนแปลงเล็ก ๆ เพียงอย่างเดียว—แต่คงเป็นการเปลี่ยนที่ต้องเขียนโค้ดเยอะ—ได้ทำลายความพอดีของแอปที่เคยเหมาะกับผู้ใช้ ผมยังใช้เวอร์ชัน 5.600 ซึ่งเป็นเวอร์ชันสุดท้ายที่มี UI เก่า และ sideload ทุกครั้งที่ซื้อเครื่องใหม่ ที่แปลกไปกว่านั้นคือผมใช้เฉพาะ POP3 สำหรับเข้าถึงบัญชี เวิร์กโฟลว์คือดูพรีวิวบนมือถือ ลบสิ่งที่ต้องลบ ถ้าจำเป็นก็ตอบกลับโดยซ่อนสำเนาถึงตัวเอง แล้วสุดท้ายค่อยดาวน์โหลดบนแล็ปท็อป ซึ่ง K9 เข้ากับเวิร์กโฟลว์นี้ได้อย่างสมบูรณ์แบบ ไม่ต้องการอะไรหรูหรา แอประดับยุค 90 ก็พอแล้ว

    • ขอบคุณมากสำหรับ ตัวอย่างที่เป็นรูปธรรม แบบนี้ มันมีค่ามากกว่าความคิดของผมที่เขียนไว้ในต้นฉบับรวมกับความคิดทั้งหมดในเธรดนี้เสียอีก
      https://news.ycombinator.com/favorites?id=akkartik&comments=t
  • ผมเองก็ยังสงสัยต่อไปว่าเส้นทางนี้จะพาไปที่ไหน สิ่งหนึ่งที่ชัดเจนคือ การทำซอฟต์แวร์คนเดียวเป็นกิจกรรมที่แตกต่างอย่างสิ้นเชิงจากการทำในทีม
    เรื่องการทดสอบนั้น การทดสอบไม่ใช่เป้าหมาย แต่เป็นเครื่องมือ ผมคิดว่าสิ่งที่เราตามหาคือ ความมั่นใจ ถ้าเรามั่นใจในการอิมพลีเมนต์ เราก็ทดสอบน้อยลง ในทางกลับกัน ถ้ามีบางอย่างที่ต้องทำงานต่อไปให้ได้เสมอ เราก็เพิ่ม integration test สักสองสามตัวที่ขอบเขตด้านนอก ซึ่งได้รับผลกระทบจากการ refactor น้อยกว่าและทำให้ความเร็วไม่ลดลงมากนัก เป็นการจิ้มเว็บแบ็กเอนด์จากภายนอก แทนที่จะทดสอบภายใน Unit test เหมาะสำหรับทำให้การออกแบบ API ใหม่เป็นรูปธรรม แต่หลังจากรู้ทิศทางแล้ว test เหล่านั้นแทบไม่มีประโยชน์

    • มีเหตุผลดี ๆ มากมายจริง ๆ ที่ควรมีการทดสอบแม้ในโปรเจกต์คนเดียว
      การตรึง if ชั่วคราวด้วยอะไรอย่าง true || เพื่อข้ามไปยังฟีเจอร์ที่กำลังทำอยู่นั้นใช้เวลาและต้องลบทิ้งทีหลัง แค่สร้าง test แล้วรัน ก็สามารถเก็บไว้เป็น regression test ได้ ถ้าคุณ deploy แอปใหญ่หรือแอปที่ช้า บางครั้งแค่ใช้ Qt ก็ทำให้ build หรือรันนานแล้ว ในกรณีนั้น test เดี่ยวจะโหลดเร็วกว่าและรันเร็วกว่า ถ้าการทำซ้ำบั๊กต้องใช้เวลา 45 วินาที ใช้ test จะดีกว่า มันช่วยอัตโนมัติส่วนที่น่าเบื่อที่สุดของงาน รักษา flow ไว้ และทำให้ตรวจสถานะบั๊กได้บ่อยเท่าที่ต้องการโดยไม่ต้องคิดทุกครั้งว่าคุ้มตรวจไหม แถมยังเก็บไว้เป็น regression test ได้ด้วย
  • ผมชอบผู้เขียนคนนี้มาก และ Mu ก็เป็นหนึ่งในโปรเจกต์ที่ผมชอบที่สุด เป็นโปรเจกต์สนุก ๆ ที่คล้าย Lisp machine สมัยใหม่ แถมรันบน QEMU ด้วย

  • ชอบประโยคที่ว่า “ซอฟต์แวร์ส่วนใหญ่ติดเชื้อจากแรงจูงใจที่จะรับใช้คนจำนวนมากในระยะสั้นจนแก้ไม่หาย” ถ้าเปลี่ยน ซอฟต์แวร์ เป็น “ธุรกิจ” ก็ยังใช้ได้เหมือนเดิม

  • เราทุกคนต่างก็ถูกความซับซ้อนในสาขาวิศวกรรมซอฟต์แวร์กดทับอยู่บ้างไม่มากก็น้อย บางครั้งความซับซ้อนนั้นก็เป็นเรื่องที่เกิดขึ้นโดยบังเอิญ
    แต่ผมไม่เห็นด้วยว่าทางออกคือการปฏิเสธไอเดียทั้งหมดที่เราสร้างกันมาตลอดหลายสิบปี ในทางกลับกัน ก็ไม่ควรรับทุกทางแก้มาใช้แบบตรงตัว หรือใช้มัน “มากเกินไป” การถูกกดทับนั้นตามนิยามแล้วเกิดขึ้นเมื่อเราใช้อะไรบางอย่างมากเกินไป จงเขียนเทสต์ ใช้ระบบควบคุมเวอร์ชัน ใช้ abstraction แต่ต้องรู้ว่าใช้มัน ทำไม ถ้า “เหตุผล” นั้นไม่เป็นจริงอีกต่อไป ก็ควรประเมินใหม่

    • ผมมองว่าแหล่งปัญหาใหญ่แหล่งหนึ่งคือ แวดวงวิชาการ ผมเป็น external examiner ให้กับนักศึกษา CS ในเดนมาร์ก และพวกเขายังคงเรียนวิธีสร้าง abstraction ล่วงหน้าตามแนวทาง object-oriented และ onion architecture
      นี่แทบจะเป็นหนึ่งในคาถาที่เลวร้ายที่สุดในการพัฒนาซอฟต์แวร์ ที่แย่กว่านั้นคือสิ่งเหล่านี้ถูกสอนกันแทบจะระดับศาสนา สิ่งที่แปลกคือ ตลอดหลายปีที่ผ่านมา วิธีที่ผู้เชี่ยวชาญเขียนซอฟต์แวร์พัฒนาไปมากแล้ว อย่างที่บอก abstraction ไม่ได้แย่โดยเนื้อแท้กับทุกสิ่ง บางทีก็ยากจะจินตนาการว่าไม่มีคลาสพื้นฐานที่ใส่ฟิลด์อย่าง updated, updated_by สำหรับข้อมูลทั่วไปที่เข้าไปในฐานข้อมูล SQL แต่โดยทั่วไปแล้ว ถ้าไม่ถูกบังคับจริง ๆ ผมแทบไม่ใช้ abstraction เลย ทว่าในแวดวงวิชาการยังคงสอนหลักสูตรแบบเดียวกับที่ผมเรียนเมื่อ 25 ปีก่อนเป๊ะ ๆ มันรู้สึกแปลกจริง ๆ เวลาต้องให้คะแนนความสามารถของนักศึกษาในการสร้าง abstraction ขนาดมหึมาด้วย UML สวย ๆ แล้วนำไป implement เป็นโค้ด ทั้งที่ 90% ของพวกเขาอาจจะไม่เห็นไดอะแกรม UML อีกสักอันเลยด้วยซ้ำ อย่างน้อยก็ในพื้นที่เล็ก ๆ ที่ผมอยู่ แต่ความจริงก็เป็นเช่นนั้น
    • เหตุผลเดียวที่ผมเริ่มใช้ Git จริง ๆ ก็คือ magit
      ผมอยากให้ทุกอย่างมี “porcelain” ระดับ command line จะดีมาก ถ้ามีเอาต์พุต --help=ui มาตรฐานและอินเทอร์เฟซสไตล์ dialog ก็น่าจะทำ automation ได้ เรื่องนี้ไม่ใช่ถูกความซับซ้อนกดทับเสียทีเดียว แต่ใกล้เคียงกับปัญหาที่ว่า ปริมาณ active muscle memory ที่เรานำมาใช้ได้มีขีดจำกัด และต้องตัดทิ้งบางส่วน ณ จุดใดจุดหนึ่ง