ตารางเกณฑ์ในสเปรดชีตที่ไม่ได้ผูกสูตร ถอดค่าคงที่กลับเป็นสูตร
ช่องที่อันตรายที่สุดในสเปรดชีตไม่ใช่ช่องที่สูตรผิด แต่เป็นช่องที่หน้าตาเหมือนผลลัพธ์ทั้งที่เป็นเลขพิมพ์มือ เพราะสูตรผิดยังไล่ย้อนได้ ส่วนเลขพิมพ์มือไม่ทิ้งร่องรอยว่ามันควรเป็นเท่าไร
พอดีมีคนส่งไฟล์สเปรดชีตมาให้ผมช่วยดู เป็นชีตที่ทีมเขาใช้สรุปตัวเลขงานประจำ ทำกันมาหลายปี โจทย์ที่ได้รับคือเขียนเครื่องมือมาช่วยตรวจว่าชีตแต่ละใบคำนวณถูกไหม ผมเปิดดูรอบแรกก็เจอของแปลกทันที คือมีตารางเกณฑ์วางอยู่ข้าง ๆ พื้นที่คำนวณ หน้าตาเรียบร้อยดี แต่ไม่มีช่องไหนในชีตอ้างถึงตารางนั้นเลยแม้แต่ช่องเดียว ไม่มี VLOOKUP ไม่มี INDEX ไม่มีอะไรทั้งนั้น คนทำงานอ่านตารางด้วยตาแล้วพิมพ์เลขลงช่องเอง
เรื่องที่ทำให้ผมต้องนั่งลงเขียนบทความนี้คือ พอเอาค่าที่พิมพ์มือมาเทียบกับตารางในชีตใบเดียวกัน มันไม่ตรง และค่าสองตัวที่ไม่ตรงนั้นเป็นตัวที่พลิกผลสรุปทั้งใบ บทความนี้ผมเขียนถึงวิธีทำงานล้วน ๆ คือ แยกช่องสูตรกับช่องพิมพ์มือออกจากกัน แล้วถอดว่าตารางค่าคงที่ที่ไม่มีใครรู้ที่มาแล้วนั้น จริง ๆ มันมาจากสูตรอะไร ตัวเลขทั้งหมดในบทความผมแต่งขึ้นใหม่เพื่อใช้เป็นตัวอย่าง ไม่ใช่ของงานจริงของใคร
ตอน port สูตร Excel มาเว็บให้ตรงทุกตำแหน่งทศนิยม เป็นเรื่องของชีตที่มีสูตรครบ แล้วย้ายมาเขียนใหม่ให้ได้ค่าตรงกันถึงหลักที่สิบสาม ส่วนตอนนี้ตรงกันข้ามเลย คือชีตที่ไม่มีสูตรตรงจุดที่สำคัญที่สุด ปัญหาไม่ใช่ความแม่น แต่เป็นการที่ไม่มีใครรู้ว่าเลขในช่องนั้นควรเป็นเท่าไร
แยกให้ออกก่อนว่าช่องไหนเป็นสูตร ช่องไหนเป็นเลขพิมพ์มือ
บนหน้าจอสองอย่างนี้หน้าตาเหมือนกันเป๊ะ แต่ในไฟล์มันคนละเรื่องกันคนละทาง ไฟล์ xlsx เป็นไฟล์บีบอัดที่ข้างในเป็น XML ช่องที่มีสูตรจะมีแท็ก <f> เก็บตัวสูตรไว้ พร้อมกับ <v> ที่เก็บผลลัพธ์ครั้งล่าสุด ส่วนช่องที่คนพิมพ์เลขลงไปเองจะมีแค่ <v> เปล่า ๆ
unzip -l book.xlsx | head
# xl/workbook.xml รายชื่อชีตกับลำดับ
# xl/worksheets/sheet1.xml ตัวเซลล์จริง
# xl/sharedStrings.xml ข้อความทั้งไฟล์ เก็บรวมไว้ที่เดียว
unzip -p book.xlsx xl/worksheets/sheet1.xml | tr '>' '>\n' | grep -A2 'r="C19"'
ผมอ่านด้วยไลบรารีมาตรฐานล้วน ไม่ลงอะไรเพิ่ม เพราะเครื่องปลายทางที่จะเอาเครื่องมือนี้ไปใช้ไม่มีสิทธิ์ลงแพ็กเกจ และงานนี้ต้องการแค่สองอย่างคืออ่านสูตรกับอ่านค่าที่แคชไว้
import zipfile, re
import xml.etree.ElementTree as ET
NS = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}"
def read_sheet(path, sheet="xl/worksheets/sheet1.xml"):
"""คืน dict ของเซลล์ -> (สูตร, ค่าที่แคชไว้, ชนิด)"""
with zipfile.ZipFile(path) as z:
shared = []
if "xl/sharedStrings.xml" in z.namelist():
root = ET.fromstring(z.read("xl/sharedStrings.xml"))
shared = ["".join(t.text or "" for t in si.iter(NS + "t"))
for si in root.findall(NS + "si")]
root = ET.fromstring(z.read(sheet))
cells = {}
for c in root.iter(NS + "c"):
ref = c.get("r")
f = c.find(NS + "f")
v = c.find(NS + "v")
val = v.text if v is not None else None
if c.get("t") == "s" and val is not None: # ข้อความอยู่ในตารางรวม
val = shared[int(val)]
cells[ref] = (f.text if f is not None else None, val,
"formula" if f is not None else "typed")
return cells
พอได้ทั้งใบมาแล้ว สิ่งแรกที่ผมทำคือพิมพ์ออกมาดูว่าช่องที่เป็นผลลัพธ์สำคัญ ๆ ช่องไหนเป็นชนิด typed บ้าง รายการที่ได้ออกมาสั้นกว่าที่คิดมาก และทุกช่องในรายการนั้นคือจุดที่ต้องเขียนตัวตรวจ
$ python audit.py sheet.xlsx --list-typed
C16 typed 60 <- ควรมาจากตารางเกณฑ์ช่วงที่ 2
C20 typed 132 <- ควรมาจากตารางตัวคูณ แถวปีที่ 18
H12 typed 1250 <- ควรมาจาก H12 = G12 * F12
C21 formula =C19*C20
C19 formula =C17-C18
C17 formula =C15*C16/100
ถ้าผูก VLOOKUP ไว้แล้วคีย์ผิด ผลลัพธ์จะเพี้ยนจนมีคนสังเกตเห็น แต่การอ่านตารางด้วยตาแล้วพิมพ์ลงช่องเอง ผลลัพธ์จะดูสมเหตุสมผลเสมอ เพราะเลขที่พิมพ์ก็อยู่ในช่วงที่ตารางนั้นมีอยู่จริง แค่ไปหยิบมาผิดแถว ในตัวอย่างที่ผมแต่งขึ้นนี้ ค่าสองตัวที่หยิบผิดแถวทำให้ผลสรุปกลับจากบวกเป็นลบ นั่นคือตัดสินใจคนละทางกันเลย
ไล่สายคำนวณจากช่องสรุปย้อนขึ้นไปหาต้นทาง
ก่อนจะตรวจอะไรได้ต้องรู้ก่อนว่าช่องสรุปกินอะไรเข้าไปบ้าง ผมไล่จากช่องท้ายสุดย้อนขึ้น แล้วเขียนสายออกมาเป็นบรรทัดเดียวให้คนอ่านรู้เรื่อง
ยอดรวมต่อรอบ = ผลบวกรายการฝั่งซ้าย - ผลบวกรายการฝั่งขวา [C15 = SUM(C9:C13) - SUM(E9:E19)]
ฐานคิด = ยอดรวมต่อรอบ x เปอร์เซ็นต์ตามช่วง [C17 = C15*C16/100]
ภาระต่อรอบ = ผลรวมรายการย่อยทุกบรรทัด [C18 = H27 = SUM(H9:H26)]
ส่วนที่เหลือ = ฐานคิด - ภาระต่อรอบ [C19 = C17-C18]
มูลค่ารวม = ส่วนที่เหลือ x ตัวคูณตามจำนวนปี [C21 = C19*C20]
พอเขียนออกมาแบบนี้จะเห็นทันทีว่าทั้งสายมีช่องพิมพ์มืออยู่สองจุดคือ C16 กับ C20 และทั้งสองจุดเป็นตัวคูณ ไม่ใช่ตัวบวก ความผิดพลาดของตัวคูณเลยไม่ได้บวกเข้าไปเฉย ๆ แต่ขยายทั้งผลลัพธ์เป็นสัดส่วน
อีกอย่างที่เจอตอนไล่สายคือช่องหนึ่งที่ชื่อหัวคอลัมน์บอกว่า "กรณีตัดรายการทั้งหมดออก" แต่สูตรข้างในเขียนว่า (C17-SUM(H9:H11))*C20 ซึ่งแปลว่ายังเก็บสามบรรทัดแรกไว้ ไม่ได้ตัดทั้งหมดตามชื่อ อันนี้ไม่ใช่ความผิดของใคร มันคือการลากช่วงเลือกเฉพาะกิจของเคสหนึ่งแล้วถูกคัดลอกต่อกันมาเรื่อย ๆ จนกลายเป็นแม่แบบ เวลาเขียนเครื่องมือมาแทนต้องให้ระบุเป็นรายการชัด ๆ ว่าจะตัดบรรทัดไหนออกบ้าง ไม่ใช่ฝังช่วงเซลล์ไว้ในสูตร
ถอดคอลัมน์ตัวคูณ 16 ค่ากลับมาเป็นสูตรเดียว
ตารางตัวคูณในชีตมี 16 แถว หน้าตาแบบนี้ ไม่มีคำอธิบาย ไม่มีที่มา มีแต่เลข
ปีที่เหลือ ตัวคูณ ปีที่เหลือ ตัวคูณ
10 90 18 132
11 96 19 136
12 102 20 140
13 108 21 143
14 113 22 146
15 119 23 150
16 123 24 152
17 128 25 155
ขั้นแรกผมดูรูปร่างของมันก่อนด้วยการหาผลต่างระหว่างแถว ได้ 6, 6, 6, 5, 6, 4, 5, 4, 4, 4, 3, 3, 4, 2, 3 ผลต่างหดลงเรื่อย ๆ แปลว่าไม่ใช่เส้นตรง และการหดแบบนี้เป็นลายเซ็นของอนุกรมเรขาคณิต ไม่ใช่ของพหุนาม ตัวเลือกที่เข้าเค้าที่สุดในงานที่คูณค่ารายงวดให้เป็นยอดรวมคือ ตัวประกอบมูลค่าปัจจุบันของเงินรายงวด ซึ่งเป็นสูตรมาตรฐานหน้าเดียวจบ
factor = [1 - (1 + i)^-n] / i
i = อัตราต่องวด
n = จำนวนงวดทั้งหมด
มีตัวแปรไม่รู้ค่าสองตัวคือ i กับวิธีแปลงเลขในคอลัมน์ซ้ายให้เป็น n ผมไม่นั่งเดา แต่กวาดหาทั้งสองตัวพร้อมกันแล้วให้โปรแกรมบอกว่าคู่ไหนตรงที่สุด
def annuity(i, n):
return (1 - (1 + i) ** -n) / i
table = {10: 90, 11: 96, 12: 102, 13: 108, 14: 113, 15: 119, 16: 123, 17: 128,
18: 132, 19: 136, 20: 140, 21: 143, 22: 146, 23: 150, 24: 152, 25: 155}
best = None
for bp in range(1, 201): # อัตราต่อเดือน 0.01% ถึง 2.00%
i = bp / 10000.0
for per_year in (1, 12): # นับเป็นปีหรือเป็นเดือน
err = max(abs(annuity(i, y * per_year) - v) for y, v in table.items())
hit = sum(round(annuity(i, y * per_year)) == v for y, v in table.items())
if best is None or (hit, -err) > (best[0], -best[1]):
best = (hit, err, i, per_year)
hit, err, i, per_year = best
print(f"ตรง {hit}/{len(table)} แถว · i={i*100:.3f}% ต่องวด · n = ปี x {per_year} "
f"· ส่วนต่างสูงสุด {err:.2f}")
ผลที่ได้คือ i เท่ากับครึ่งเปอร์เซ็นต์ต่อเดือน และ n คือจำนวนปีคูณสิบสอง ตรงทั้ง 16 แถวเมื่อปัดเป็นจำนวนเต็ม ส่วนต่างสูงสุดก่อนปัดอยู่ที่ครึ่งหน่วย พอรู้แบบนี้แล้วตารางที่เคยเป็นเลขลอย ๆ ก็กลายเป็นของที่อธิบายได้ทั้งชุด และที่สำคัญกว่าคือขยายออกนอกช่วงได้
สูตรที่เข้าเค้าหนึ่งสูตร ยังไม่ใช่คำตอบ ต้องพยายามทำให้มันตกก่อน
ตรงนี้เป็นจุดที่ผมระวังตัวมากที่สุด เพราะตัวเลขสิบหกค่ากับสูตรที่มีตัวแปรอิสระสองตัว มันหาอะไรมาทาบให้ใกล้เคียงได้ไม่ยากเลย ถ้าไม่ตั้งเกณฑ์ไว้ก่อนว่าแค่ไหนถึงจะเรียกว่าใช่ เราจะหลอกตัวเองได้ง่ายมาก
เกณฑ์ที่ผมใช้มีสามข้อ หนึ่งคือต้องตรงทุกแถวเมื่อปัดเป็นจำนวนเต็ม ไม่ใช่ตรงเกือบหมด สองคือส่วนต่างก่อนปัดต้องไม่เกินครึ่งหน่วยทุกแถว ถ้ามีแถวไหนเกินแปลว่ารูปแบบยังไม่ใช่ สามคือพารามิเตอร์ที่ได้ต้องเป็นเลขที่คนตั้งขึ้นมาจริง ครึ่งเปอร์เซ็นต์ต่อเดือนเป็นเลขที่คนพูดกันในที่ประชุมได้ แต่ถ้ากวาดแล้วได้ค่าอย่าง 0.4873 เปอร์เซ็นต์ ให้สงสัยไว้ก่อนว่ากำลังดัดสูตรให้เข้ากับข้อมูล ไม่ใช่เจอสูตรที่คนเขาใช้จริง
# ก่อนจะเชื่อ ลองรูปแบบง่ายกว่าด้วย ถ้ามันอธิบายได้พอกันแปลว่ายังไม่ได้คำตอบ
import statistics
ys = sorted(table)
vs = [table[y] for y in ys]
lin = statistics.linear_regression(ys, vs) # เส้นตรง
lin_err = max(abs(lin.slope * y + lin.intercept - table[y]) for y in ys)
ann_err = max(abs(annuity(0.005, y * 12) - table[y]) for y in ys)
print(f"เส้นตรง ส่วนต่างสูงสุด {lin_err:.2f} · สูตรเงินรายงวด {ann_err:.2f}")
# เส้นตรง ส่วนต่างสูงสุด 5.31 · สูตรเงินรายงวด 0.50
ส่วนต่างห้าหน่วยกับครึ่งหน่วยห่างกันสิบเท่า อันนี้ชัดพอที่จะตัดสิน แต่ถ้าสองตัวเลือกให้ผลใกล้กัน ผมจะไม่สรุปว่าเจอแล้ว และจะเขียนลงเอกสารไว้ตรง ๆ ว่ายังไม่รู้ที่มา ดีกว่าใส่สูตรที่เดาเอาไว้ในเครื่องมือแล้วมีคนเอาไปใช้ต่อโดยคิดว่าเป็นของยืนยันแล้ว
ตารางเป็นเลขปัดแล้ว ในช่วงต้องใช้ตาราง นอกช่วงคิดจากสูตรแต่ต้องเตือน
ข้อนี้เป็นข้อที่คนเขียนโปรแกรมชอบทำพลาด พอถอดสูตรได้แล้วจะรู้สึกว่าสูตรแม่นกว่า เลยเปลี่ยนไปใช้สูตรทั้งหมดเลย ซึ่งผิด เพราะผลที่ทีมเคยออกไปก่อนหน้านี้ทั้งหมดคำนวณจากตาราง ถ้าเราเปลี่ยนไปใช้สูตร ผลของเคสเดิมจะขยับทันทีโดยไม่มีใครสั่ง
กติกาที่ผมเขียนไว้ในเครื่องมือเลยเป็นแบบนี้
ยึดตารางเสมอ
ตารางคือของที่ทีมใช้จริงและผลเก่าทั้งหมดอ้างอิงมัน สูตรมีไว้อธิบายที่มาเท่านั้น ห้ามเอาค่าจากสูตรไปแทนค่าในช่วงนี้ ต่อให้ต่างกันแค่ครึ่งหน่วยก็ห้าม
คิดจากสูตรได้ แต่ต้องติดป้าย
คำนวณให้ได้ตามปกติ แต่ผลลัพธ์ต้องมีคำเตือนแนบไปด้วยทุกครั้งว่า ค่านี้อยู่นอกตารางที่ทีมใช้ คนอ่านจะได้รู้ว่าต้องไปยืนยันกับคนตั้งเกณฑ์ก่อน ไม่ใช่เอาไปใช้ต่อเงียบ ๆ
การเขียนแบบนี้ทำให้เครื่องมือทำสองหน้าที่ได้พร้อมกัน คือทำซ้ำของเดิมได้เป๊ะสำหรับเคสในช่วง และไม่ตายตอนเจอเคสนอกช่วง
ตัวตรวจต้องคิดตามที่กรอก แล้วรายงานส่วนต่าง ไม่ใช่แก้ให้เอง
ตอนออกแบบผมเถียงกับตัวเองอยู่พักหนึ่งว่าจะให้เครื่องมือแก้ค่าให้เลยไหม สรุปว่าไม่ เพราะเราไม่รู้ว่าค่าที่คนพิมพ์เข้ามาเป็นความผิดพลาดหรือเป็นการตัดสินใจ ถ้าเครื่องมือไปแก้ให้เอง สิ่งที่หายไปคือข้อมูลว่ามีคนตัดสินใจต่างจากตาราง ซึ่งเป็นข้อมูลที่มีค่ากว่าตัวเลขที่ถูกต้องเสียอีก
หน้าตาผลลัพธ์ที่ผมให้มันพิมพ์ออกมาเป็นแบบนี้
$ python audit.py review sheet.xlsx
ช่องพิมพ์มือที่ไม่ตรงตารางในชีตเอง
C16 กรอก 70 ตารางช่วงนี้ให้ 60 -> ฐานคิด 37,964 แทนที่จะเป็น 32,541
C20 กรอก 165 ตารางปีที่ 19 ให้ 136 -> ตัวคูณสูงกว่าเกณฑ์ 21%
ผลรวมที่ไม่ตรง
H27 ในชีต 35,063.74 คำนวณใหม่ได้ 35,064.62 ส่วนต่าง 0.88
ถ้าคิดตามตารางทั้งหมด
ส่วนที่เหลือ = 32,541 - 35,064.62 = -2,523.62 (ติดลบ)
สรุป: ค่าที่กรอกให้ผลเป็นบวก ค่าตามตารางให้ผลติดลบ ต้องให้คนตัดสิน
ตอนเขียนโค้ดเราจะเผลอใส่ max(0, x) กันโดยอัตโนมัติเพราะผลติดลบมันดูไม่เข้าท่า แต่ในงานตรวจ ค่าติดลบคือคำตอบที่มีความหมายที่สุดในไฟล์ มันแปลว่าเงื่อนไขไม่ผ่าน การกลบให้เป็นศูนย์เท่ากับลบข้อสรุปทิ้งไปทั้งข้อ แล้วคนอ่านจะเห็นเป็นแค่ยอดน้อย ๆ แทนที่จะเห็นว่ามันไม่ผ่าน
รายการที่ผมให้ตัวตรวจไล่มีเจ็ดข้อ ทุกข้อเป็นการเทียบสองฝั่งแล้วรายงานส่วนต่าง ไม่มีข้อไหนที่มันเขียนทับค่าในไฟล์
- ช่องพิมพ์มือทุกช่องที่ควรมาจากตาราง เทียบกับค่าที่ตารางให้ แล้วบอกว่าถ้าใช้ค่าตารางผลจะเป็นเท่าไร
- ผลรวมทุกช่อง บวกใหม่จากรายการย่อยแล้วเทียบกับค่าที่แคชไว้ในชีต
- รายการย่อยที่คิดจากเปอร์เซ็นต์ คิดใหม่จากฐานของบรรทัดนั้น แล้วเทียบทีละบรรทัด
- ค่าที่อยู่นอกช่วงตาราง คิดจากสูตรได้แต่ต้องติดป้ายกำกับไว้ทุกครั้ง
- ผลลัพธ์ติดลบ รายงานตรง ๆ ว่าไม่ผ่าน ห้ามกลบเป็นศูนย์
- สูตรที่ชื่อหัวคอลัมน์ไม่ตรงกับช่วงที่ลาก รายงานว่าเป็นการเลือกเฉพาะกิจ ไม่ใช่กฎ
- ช่องที่เคยเป็นสูตรแล้วกลายเป็นเลข เทียบกับแม่แบบว่าเดิมช่องนี้เป็นชนิดไหน
ข้อสุดท้ายนี่เจอบ่อยกว่าที่คิด เกิดจากการคัดลอกแล้ววางแบบค่าอย่างเดียวทับช่องที่เคยมีสูตร หน้าตาบนจอไม่เปลี่ยนเลยสักนิด แต่ตั้งแต่วินาทีนั้นช่องนั้นก็ไม่อัปเดตตามอินพุตอีกแล้ว การเทียบชนิดช่องกับแม่แบบเป็นทางเดียวที่จับได้
สิ่งที่ถอดไม่ได้ ต้องเขียนไว้ตรง ๆ ว่ายังไม่ยืนยัน
เครื่องมือที่ผมทำถอดมาจากชีตตัวอย่างจำนวนน้อยใบ ซึ่งแปลว่ามีหลายอย่างที่ยังไม่รู้จริง ผมเลยเปิดหัวข้อหนึ่งในเอกสารประกอบไว้เป็นรายการของที่ยังไม่ยืนยัน และพิมพ์รายการนั้นออกมาท้ายรายงานทุกครั้งที่รัน ไม่ได้ซ่อนไว้ในไฟล์ที่ไม่มีใครเปิด
ยังไม่ยืนยัน (ถอดจากตัวอย่างจำนวนน้อย ต้องถามคนตั้งเกณฑ์)
1. เปอร์เซ็นต์ตามช่วง อิงฐานตัวไหน ตัวอย่างที่มีทุกตัวตกช่วงเดียวกันหมด แยกไม่ออก
2. ทำไมใบนี้ถึงใช้ค่าที่สูงกว่าตาราง เกณฑ์ต่างจริงหรือหยิบผิดแถว
3. อัตราต่องวดกับจำนวนปีตั้งต้น เป็นค่ามาตรฐานหรือปรับตามงาน
4. นอกช่วง 10 ถึง 25 ปี ทีมใช้อะไร ตอนนี้เครื่องมือคิดจากสูตรแล้วติดป้ายไว้
5. หมายเหตุข้างรายการย่อย มีผลต่อการตัดสินไหม ในชีตไม่เข้าสูตรเลย
ข้อที่หนึ่งเป็นตัวอย่างที่ดีของข้อจำกัดแบบที่โปรแกรมบอกเองไม่ได้ ถ้าตัวอย่างที่มีอยู่ทุกใบให้ค่าเดียวกันหมดไม่ว่าจะใช้ฐานไหนคิด เราก็แยกไม่ออกว่าเกณฑ์อ้างฐานตัวไหน ต้องรอเคสที่ค่าต่างกันหรือรอคนตอบเท่านั้น การเดาแล้วฝังลงโค้ดตรงนี้อันตรายกว่าการปล่อยว่างไว้ เพราะวันที่มันผิดจะไม่มีใครรู้ว่าเคยเดาไว้
ตัวเลขจริงในไฟล์ อย่าให้หลุดเข้าไปในเทสต์หรือในเครื่องมือ
ไฟล์พวกนี้มักมีข้อมูลของคนอื่นอยู่เต็ม ผมเลยวางกฎไว้ตั้งแต่ต้นว่าไฟล์จริงอยู่ได้ที่เดียวคือโฟลเดอร์งานที่ถูกกันไม่ให้เข้าระบบเก็บเวอร์ชัน ส่วนเทสต์ทั้งหมดใช้ไฟล์ที่ปั้นขึ้นมาเอง
การปั้นไฟล์ทดสอบไม่ยากเลย เพราะ xlsx เป็นแค่ zip เราเขียน XML ขั้นต่ำแล้วซิปเองได้ ไม่ต้องลงไลบรารีอะไรทั้งนั้น
import zipfile
SHEET = """<?xml version="1.0" encoding="UTF-8"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="15"><c r="C15"><f>SUM(C9:C13)-SUM(E9:E19)</f><v>54235</v></c></row>
<row r="16"><c r="C16"><v>70</v></c></row>
<row r="17"><c r="C17"><f>C15*C16/100</f><v>37964.5</v></c></row>
<row r="20"><c r="C20"><v>165</v></c></row>
</sheetData></worksheet>"""
def make_fixture(path):
with zipfile.ZipFile(path, "w") as z:
z.writestr("xl/worksheets/sheet1.xml", SHEET)
# เทสต์ที่ต้องมีอย่างน้อยสามข้อ
# 1) ป้อนค่าที่พิมพ์มือเดิม ต้องได้ผลเท่ากับที่ชีตแสดงอยู่เป๊ะ
# 2) ป้อนค่าตามตาราง ต้องได้ผลติดลบและรายงานว่าไม่ผ่าน
# 3) ตารางตัวคูณ ปีที่ 10 ต้องได้ 90 และปีที่ 25 ต้องได้ 155 จากตาราง ไม่ใช่จากสูตร
เทสต์ข้อที่สามสำคัญกว่าที่หน้าตามันบอก เพราะมันเป็นตัวกันไม่ให้ใครในอนาคตมาเปลี่ยนไปใช้สูตรทั้งหมดด้วยความหวังดี ถ้าวันไหนมีคนแก้แล้วเทสต์ข้อนี้แดง จะได้รู้ตัวก่อนที่ผลของเคสเก่าจะขยับ
ส่วนตัวผมว่างานถอดค่าคงที่แบบนี้คุ้มเวลาที่ลงไปเสมอ ไม่ใช่เพราะได้สูตรสวย ๆ มาหนึ่งบรรทัด แต่เพราะหลังจากถอดได้แล้วเราตอบได้ว่าเลขในตารางถูกหรือผิด และตอบได้ว่านอกช่วงตารางควรทำยังไง ซึ่งก่อนหน้านั้นไม่มีใครในทีมตอบได้เลยสักคน ใครจะเอาวิธีกวาดหาพารามิเตอร์ไปใช้กับตารางค่าคงที่ชุดอื่นก็ตามสะดวกครับ ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย