help.autocorrect=1 ของ Git จะแก้คำสั่งที่พิมพ์ผิดแล้วรอเพียง 0.1 วินาที ก่อนสั่งรัน จึงแทบจะเหมือนรันทันทีจนผู้ใช้ไม่มีเวลาตรวจสอบหรือยกเลิก
- พฤติกรรมนี้ไม่ใช่ค่าเริ่มต้นของ Git; ในการตั้งค่าเริ่มต้น Git จะไม่รันคำสั่งที่ไม่รู้จัก แต่จะแสดงเฉพาะ คำสั่งที่คล้ายกัน แล้วจบการทำงาน
- ในการถกเถียงเมื่อปี 2008
help.autocorrect ถูกเปลี่ยนจากค่าบูลีนเป็น เวลารอในหน่วย decisecond ทำให้ 1 ถูกตีความว่าเป็นการรอ 0.1 วินาที ไม่ใช่ true
- ปัจจุบัน
help.autocorrect รองรับ 0, จำนวนบวก, immediate, prompt, never และหากต้องการให้ยืนยันก่อนรัน prompt เป็นตัวเลือกที่ปลอดภัยกว่า
- การคาดเดาคำสั่งของ Git อิงจาก Levenshtein distance แบบปรับแก้ และ Scott Chacon ได้เสนอแพตช์ให้จัดการ
1 กับสตริงบูลีนให้เป็นธรรมชาติมากขึ้น
จาก git pushy ไปเป็นการรัน git push
- เมื่อ
@dhh พิมพ์ git pushy Git คาดเดาว่านั่นคือ git push และรอเพียง 0.1 วินาที ก่อนรัน
- 0.1 วินาทีสั้นเกินไปที่ผู้ใช้จะอ่านคำสั่ง ตัดสินใจว่าถูกต้องหรือไม่ และยกเลิกได้
- พฤติกรรมนี้ไม่ใช่พฤติกรรมเริ่มต้นของ Git แต่เกิดขึ้นเมื่อเปิดการตั้งค่า
help.autocorrect
พฤติกรรมเริ่มต้นและจุดกำเนิดของ help.autocorrect
- การตอบสนองเริ่มต้นของ Git คือจะไม่รันคำสั่งที่ไม่รู้จัก แต่จะแสดงคำสั่งที่เป็นไปได้ตามความคล้ายของสตริง แล้วจบการทำงาน
- ตัวอย่าง:
git pushy อาจแสดงว่า push เป็นคำสั่งที่คล้ายที่สุด
- เดิมที Git ตอบคำสั่งที่ไม่รู้จักเพียงว่า “ไม่ใช่คำสั่ง Git” เท่านั้น
- ในปี 2008 Johannes Schindelin เสนอแพตช์เล็ก ๆ ที่ไล่ดูคำสั่งที่รู้จักเพื่อหาคำสั่งที่คล้ายที่สุด และถ้ามีผู้สมัครที่ใกล้เคียงเพียงรายการเดียวก็ให้รัน
- Alex Riesen ออกแพตช์ที่ควบคุมสิ่งนี้ด้วยการตั้งค่า
help.autocorrect โดยในช่วงแรกการตั้งค่านี้เป็น บูลีน
- ในการตั้งค่าของ Git ค่าบูลีนสามารถตีความ
1 เป็น true ได้
- ดังนั้นในบริบทเดิม
help.autocorrect=1 จึงอาจดูเหมือนค่าที่เปิดการแก้อัตโนมัติ
เหตุใด 1 จึงกลายเป็น 0.1 วินาที
- Junio Hamano เสนอให้
help.autocorrect ไม่ใช่บูลีนธรรมดา แต่เป็น จำนวน decisecond ที่ต้องรอก่อนรัน
- decisecond คือหน่วยละ 0.1 วินาที
- เจตนาคือให้ผู้ใช้มีโอกาสกด
^C
- ในแพตช์ที่ถูกรับเข้าไปภายหลัง ค่าการตั้งค่าถูกเปลี่ยนเป็นเวลารอ
- ผลก็คือ
help.autocorrect=1 ไม่ได้หมายถึง “เปิดฟีเจอร์” แต่หมายถึง “รอ 1 decisecond หรือ 0.1 วินาที แล้วจึงรัน”
- การถกเถียงไม่ได้ระบุไว้ว่าทำไมจึงเลือก decisecond เป็นหน่วยเวลาที่เหมาะสม
ค่าการตั้งค่าที่เลือกได้ในปัจจุบัน
- ตามเอกสารของ Git
help.autocorrect รองรับค่าต่อไปนี้
0: ค่าเริ่มต้น และจะแสดงเฉพาะคำสั่งที่แนะนำ
- จำนวนบวก: รันคำสั่งที่แนะนำหลังผ่านไปตามจำนวน decisecond ที่กำหนด
immediate: รันคำสั่งที่แนะนำทันที
prompt: แสดงคำแนะนำและถามยืนยันว่าจะรันหรือไม่
never: ไม่รันและไม่แสดงคำสั่งที่แนะนำ
- หากต้องการให้ยืนยันก่อนรัน
prompt เหมาะสมกว่าสำหรับผู้ใช้ส่วนใหญ่
- ตัวอย่างการตั้งค่าเป็นดังนี้
git config --global help.autocorrect prompt
- หลังจากนั้น เมื่อป้อนคำสั่งอย่าง
git pushy ระบบจะถามด้วย [y/N] ว่าจะรัน push หรือไม่
วิธีที่ Git เลือกคำสั่งที่คล้ายกัน
- Git ไม่ได้คาดเดาคำสั่งแบบสุ่ม และตั้งแต่จุดหนึ่งเป็นต้นไปจะถือว่าไกลเกินไปจนไม่เสนอผู้สมัคร
- ตัวอย่างพฤติกรรมมีดังนี้
git bass ใกล้กับ rebase มากพอจนแสดงการถามยืนยันการรัน
git bassa ถูกตัดสินว่าไม่เหมือน rebase อีกต่อไป จึงไม่รันผู้สมัคร
git dm แสดงทั้ง am และ rm เป็นคำสั่งที่คล้ายกัน
git dma จับคู่กับ am เพียงรายการเดียว
- ตรรกะนี้อิงจากอัลกอริทึม Levenshtein distance แบบปรับแก้
- คำนวณต้นทุนการแก้ไขอักขระที่ใช้ในการเปลี่ยนสตริงหนึ่งให้เป็นอีกสตริงหนึ่ง
- บางการดำเนินการถูกมองว่ามีต้นทุนแพงกว่าการดำเนินการอื่น
- Git มี cutoff แบบฮาร์ดโค้ดไว้ หากต้นทุนเมื่อเทียบกับคำสั่งที่รู้จักสูงเกินไป ก็จะถือว่าผู้ใช้พิมพ์ผิดอย่างมากและไม่คาดเดา
แพตช์ที่พยายามเปลี่ยนการตีความ 1
- เนื่องจาก
help.autocorrect=1 ในมุมของมนุษย์แทบจะเหมือน immediate Scott Chacon จึงเขียนแพตช์เล็ก ๆ ให้ตีความ 1 เป็น รันทันที แทนการรอ 0.1 วินาที
- Junio Hamano ขอว่าอย่าจัดการเฉพาะ
"1" เป็นกรณีพิเศษ แต่ให้ตีความ ค่าสตริงบูลีน อย่าง yes, no, true, off ให้ถูกต้องด้วย
- แพตช์เวอร์ชันที่สองกำลังเดินหน้าไปในทิศทางที่รวมการจัดการสตริงบูลีนเหล่านี้ด้วย
- หากแพตช์ถูกรวมเข้าไป ความสับสนเกี่ยวกับ
help.autocorrect ใน Git เวอร์ชันอนาคตอาจลดลงได้
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ในยุค 70 Hal Finney กำลังพยายามยัด BASIC interpreter สำหรับ Mattel Intellivision ให้ลงใน ROM ขนาด 2KB ซึ่งทุกไบต์มีค่า
เพื่อแสดงข้อผิดพลาดทางไวยากรณ์ เขาย่อข้อความ error ทั้งหมดเหลือ
EH?และทุกวันนี้ก็ยังขำที่เจ้าตัวค่อนข้างภูมิใจกับเรื่องนั้นEH?น่าจะเข้ากันได้ดีในฐานะคำตอบจาก text parser ของเกม interactive fiction สมัยเก่าที่เกี่ยวข้องกันนิดหน่อย BASIC รุ่นเก่าบางสายพันธุ์ก็ใช้
?แทนคำสั่งPRINTด้วย ซึ่งดูเหมือนมีเป้าหมายเพื่อประหยัดเวลาพิมพ์ใน REPL มากกว่าประหยัดหน่วยความจำedใช้แค่?ตัวเดียวกับทุก error เลย ประหยัดกว่า 3 เท่าEh?เป็นคำตอบของ interpreter คือใน JOSS ของ RAND[https://en.wikipedia.org/wiki/JOSS#/media/File:JOSS_Session....](https://en.wikipedia.org/wiki/JOSS#/media/File:JOSS_Session.jpg)
https://en.wikipedia.org/wiki/JOSS
หน่วยความจำประมาณ 5KB แต่เมื่อเทียบกับ Intellivision แล้ว เครื่องหนักราว 5,000 ปอนด์
ดังนั้นอาจเดาคำหนึ่งแล้วตัวอักษรทั้ง 5 เป็นสีเขียวทั้งหมด แต่กลับไม่ใช่คำตอบทั้งสองคำก็ได้ เวลานั้นผมให้ขึ้นแค่
Huh?ทางด้านขวา แล้วคนชอบส่วนนั้นกันต้นตอของปัญหาคือ ตั้งชื่อ config ได้แย่
ถ้าตั้งค่าเดิมถูกตั้งชื่อให้ดูเป็นบูลีนอย่าง
help.autocorrect_enabledคำขอให้รับค่า integer แล้วตีความเป็นหน่วย 0.1 วินาทีก็คงฟังไม่ขึ้น ต้องมี config อื่นอย่างhelp.autocorrect_accept_after_dsecแทน และคำว่าdsecก็ไม่คุ้นเคยเอามาก ๆ จนคนใช้คงต้องไปค้นดูเองใน code review ผมมักขอเรื่องแบบนี้เสมอ ถ้ามีโอกาสคลุมเครือ ชื่อตัวแปรต้องมี หน่วย อยู่ด้วย เช่น
int timeoutควรเป็นint timeout_msecสำหรับชื่อ config เรื่องนี้สำคัญกว่าถึง 100 เท่า เพราะ config เป็นส่วนหนึ่งของ public interface และภายหลังเปลี่ยนไม่ได้
ในบริบท C++ เกณฑ์คร่าว ๆ คือ
void FooBar(std::chrono::milliseconds timeout);ใช้ได้ เพราะเป็น function signature เวลาอ่านจะเห็น type ไปด้วยแต่สำหรับตัวแปร
timeoutใช้ไม่ได้ ใน 99% ของกรณี การใช้งานจริงจะเห็นเป็นauto timeout = gl_timeout;หรือFooBar(timeout);อะไรแบบนี้เพราะใน C++ ใช้
autoกันทั่วไป การไล่ดู type ที่แน่ชัดตอนสำคัญจึงค่อนข้างน่ารำคาญเวลาทำงาน C++ ผมใช้ IDE หรือ editor ที่มี language server แต่ไม่มีเวลามานั่งเอาเมาส์ชี้ดู type ทุก 5 วินาที
ถ้าเห็นข้อความว่า “จะรันหลังจาก 1ms” ก็คงรีบไปเช็ก config ทันที
TimoutSec=60และTimeoutSec=1minผมว่าการไม่ใส่หน่วยไว้ในชื่ออาจดีกว่าด้วยซ้ำ แต่ถ้าไม่นับกรณีพิเศษนั้น ก็เห็นด้วยทั้งหมด
ทางเลือกที่ดีที่สุดที่พบจนถึงตอนนี้คือให้ค่ารับหน่วยไปด้วย เช่นยอมรับ
5 secondsหรือ5sและถือว่า1เฉย ๆ เป็นค่าที่ผิดintแบบนั้นเอง และแนะนำให้ใช้ value class ที่ส่งหน่วยไปอย่างถูกต้องแทนเช่น
Second(2)หรือMillisecond(2000)แน่นอนว่าเรื่องนี้ขึ้นกับความสามารถและธรรมเนียมของภาษา
xmobar ก็ใช้ หน่วย 0.1 วินาที ในจุดที่คล้ายกันแต่มีปัญหากว่า เป็น config ที่ประกาศว่าจะ refresh แต่ละ section บ่อยแค่ไหน
ตัวเลขใน config ตัวอย่างเล็กเกินไปจนดูไม่น่าใช่มิลลิวินาที จึงทำให้คนเดาอย่างสมเหตุสมผลว่าเป็นวินาที และสุดท้ายคำสั่งถูกรันบ่อยกว่าที่ตั้งใจ 10 เท่า
ผมเคยเห็นโหลดพุ่งโดยไม่ตั้งใจจากปัญหานี้หลายครั้ง
ดูเป็นการออกแบบที่ค่อนข้างแย่
แต่ข้อ 1 เป็นผลจากการอ่านบทความผิด และ “ค่าเดิม” นั้นไม่เคยมีอยู่ใน git
1=trueเป็น1=หน่วงเวลายืนยัน 0.100msเป็นสิ่งที่ควรหลีกเลี่ยงแทบทุกครั้ง ถ้าเคยมีค่าเดิมอยู่ ก็คงอยากลบทิ้ง แต่ก็ควรเปลี่ยนชื่อ config ด้วยhelp.autocorrectเป็นเวลา และวัดด้วยหน่วยที่ไม่เป็นมาตรฐานสำหรับผู้ใช้ส่วนใหญ่ ก็แย่อยู่แล้ว ควรให้เปิดใช้ด้วยบูลีน และให้ปรับเวลายืนยันด้วยค่าทศนิยมการเปลี่ยนนั้นเกิดขึ้นในปี 2008 ระหว่าง code review ของแพตช์แรกเริ่มที่จะเพิ่ม option นั้นเป็นบูลีน และเกิดก่อนที่จะ commit เข้า main git tree
อันนี้ดูเหมือน กิ่งก้านที่โตขึ้นเองแบบอินทรีย์ มากกว่าการออกแบบ
ผมว่านี่เป็นตัวอย่างที่ดีของ feature creep (การเพิ่มฟีเจอร์ลุกลาม)
มองในแง่ดีก็นำความซับซ้อนที่ไม่จำเป็นเข้ามา มองในแง่ร้ายก็ทำให้คนที่พึ่งพามันใส่ใจกับสิ่งที่ตัวเองทำอยู่น้อยลง
กดลูกศรขึ้นแล้วแก้คำผิดมันยากขนาดนั้นเลยเหรอ? การรันสิ่งที่ “ใกล้เคียงพอ” โดยอัตโนมัติเป็นสูตรสำเร็จของการทำสิ่งที่ไม่ได้ตั้งใจ
จุดที่ผิดพลาดอยู่ตรงนี้ คือไม่ควรนำค่าตั้งค่าเดิมไปใช้ซ้ำในความหมายอื่น แต่ควรเพิ่ม ค่าตั้งค่าใหม่
help.autocorrectควรแยกเป็นเปิด/ปิด ส่วนhelp.autocorrect.millisecondsควรเป็นระยะเวลาที่ต้องรอระบบอื่นก็มีความผิดพลาดคล้ายกัน เช่น
innodb_flush_log_at_trx_commitของ MySQL ถ้าเป็น 0 คือปิดใช้งาน, 1 คือเปิดใช้งาน และ 2 ถูกเพิ่มเข้ามาภายหลังด้วยความหมายพิเศษ1หมายถึงอะไรตอนนี้
1ถูกคงไว้เป็นค่าบูลีนจริง ขณะที่จำนวนเต็มอื่นถูกทำให้เป็นหน่วย 0.1 วินาที ยิ่งถลำลึกลงไปอีกเป็นตรรกะที่น่าสงสัยแบบเดียวกับ
no == falseอันฉาวโฉ่ของ YAMLตามบันทึก เวลาตอบสนองแบบสุ่มที่เร็วที่สุดอยู่ราว 1.5ds และบังเอิญว่าเวลาตอบสนองเฉลี่ยของเกมเมอร์ก็ประมาณนั้น แต่การตอบสนองที่ไม่ใช่แบบสุ่มอาจเร็วกว่าได้มาก เช่น กรณีตอบสนองตามจังหวะ
ถ้าอยากให้เร็วขนาดนั้นก็ทำได้ และความเร็วในการเรียกใช้ก็ควรจะค่อนข้างคงที่ น่าจะอยู่ระดับไม่กี่มิลลิวินาที
เมื่อ 1 ปีก่อนเปิดฟีเจอร์แก้อัตโนมัติและตั้งไว้ 3 วินาที แล้วรู้สึกได้สองอย่าง
พออ่านบทความนี้แล้ว ก็กลายเป็นแรงผลักดันพอให้ปิดมันหลังจากผ่านไป 1 ปี
แน่นอนว่ามันก็ยัง “ปนเปื้อน” ประวัติของเชลล์อยู่ดี
ถึงจะย้อนประวัติกลับไปแล้วเอาคำสั่งที่ผิดล่าสุดมาใช้ซ้ำทั้งอย่างนั้น เพราะยังไงมันก็ทำงานได้ ก็ไม่ต่างกัน
หน่วย 0.1 วินาที เป็นตัวเลือกที่แปลกมากจริง ๆ
เวลาหน่วงควรกำหนดเป็นมิลลิวินาทีหรือวินาทีมากกว่า และทั้งสองหน่วยก็ใช้กันบ่อยกว่ามากในวงการคอมพิวติ้ง
decisecondsเป็นหน่วยเฉพาะของ git หมายถึง decision-seconds แบบ “วินาทีที่ต้องใช้ในการตัดสินใจ” xDอนึ่ง ภาษาอังกฤษไม่ใช่ภาษาแม่ของผม แต่ผมมาจากภูมิภาคศิวิไลซ์ที่ใช้ระบบเมตริก
ความต่างระหว่าง 0 วินาทีกับ 1 วินาทีนั้นยาวจนสังเกตได้เมื่อรออะไรบางอย่าง แต่ความต่างระหว่าง n มิลลิวินาทีกับ n+1 มิลลิวินาทีนั้นเล็กเกินกว่าจะใช้งานให้เป็นประโยชน์
เวลาตอบสนองแตกต่างกันตามชนิดของสิ่งเร้า โดยเสียงจะเร็วกว่า ภาพเล็กน้อย และสัมผัสจะเร็วกว่าเสียงอีกนิด อยู่ราว 90~180ms
ดังนั้นถ้า git ต่อยคุณสักหมัดแทนการแสดงข้อความผิดพลาด ก็อาจพอมีเวลาตอบสนองได้แบบเฉียดฉิว
10ms จะพอไหมนะ?
อ่านบทความนี้แล้วนึกถึงคำว่า โบราณคดีซอฟต์แวร์ และ “นักโบราณคดีโปรแกรมเมอร์”
แนวคิดหลังต้องขอบคุณ Vernor Vinge
แต่แนวคิดนั้นถูกบอกใบ้สั้น ๆ ในบทนำเท่านั้น แล้วหลังจากนั้นก็ไม่มีอีกเลย ผลของโบราณคดีเกี่ยวข้องกับพล็อตก็จริง แต่ “นักโบราณคดีซอฟต์แวร์” ไม่ใช่ตัวละครที่มีความหมายต่อเรื่องเล่า
รู้สึกเหมือนโดนหลอกให้คลิก