ฮาร์ดโค้ด คือการปฏิบัติในการวางค่าที่ไม่สามารถเปลี่ยนแปลงได้ลงในซอร์สโค้ดโดยตรง แทนที่จะแยกออกไปยังแหล่งข้อมูลภายนอก ตามการสำรวจนักพัฒนา Stack Overflow ปี 2024 นักพัฒนากว่า 67% ประสบปัญหาที่เกิดจากพารามิเตอร์ที่ถูกฮาร์ดโค้ดเป็นประจำ เทคนิคการเขียนโปรแกรมนี้ขัดต่อหลักการของการพัฒนาที่ยืดหยุ่น และสร้างความเสี่ยงร้ายแรงเมื่อย้ายแอปพลิเคชันระหว่างสภาพแวดล้อม — จากเครื่องท้องถิ่นไปยังเซิร์ฟเวอร์โปรดักชัน
ประเด็นสำคัญ
ฮาร์ดโค้ด (การเขียนโค้ดแบบตายตัว) เป็นแอนตี้แพทเทิร์นที่ข้อมูล พารามิเตอร์การกำหนดค่า หรือค่าต่างๆ ถูกฝังลงในข้อความโปรแกรมโดยตรง แทนที่จะอ่านค่าจากแหล่งภายนอก นักพัฒนาเขียนค่าเหล่านั้นเป็นลิเทอรัล — สตริง ตัวเลข ค่าบูลีน — โดยตรงในเนื้อหาของฟังก์ชัน คลาส หรือโมดูล คำนี้เกิดขึ้นในชุมชนนักพัฒนาในช่วงทศวรรษ 1980 เมื่อซอฟต์แวร์เริ่มแพร่กระจายไปยังแพลตฟอร์มฮาร์ดแวร์ต่างๆ และเห็นได้ชัดว่าพารามิเตอร์ที่ถูกฮาร์ดโค้ดเป็นอุปสรรคต่อการพกพา
ปัญหาหลักของฮาร์ดโค้ดคือการเปลี่ยนแปลงค่าใดๆ ดังกล่าวต้องแก้ไขซอร์สโค้ด คอมไพล์ใหม่ และปรับใช้แอปพลิเคชันใหม่ ทำให้กระบวนการอัปเดตช้า มีแนวโน้มเกิดข้อผิดพลาด และอันตราย — นักพัฒนาอาจเปลี่ยนแปลงอย่างอื่นในโค้ดโดยไม่ตั้งใจขณะแก้ไขพารามิเตอร์ที่ถูกฮาร์ดโค้ด ในแนวทางปฏิบัติ DevOps สมัยใหม่ วิธีการนี้ไม่แนะนำอย่างยิ่ง
ตามการศึกษาVeracode State of Software Security 2024 ประมาณ 23% ของช่องโหว่ทั้งหมดในแอปพลิเคชันเชิงพาณิชย์เกี่ยวข้องกับข้อมูลรับรองที่ถูกฮาร์ดโค้ด ทำให้การต่อสู้กับฮาร์ดโค้ดไม่ใช่เพียงเรื่องความสะดวกสบาย แต่เป็นงานด้านความปลอดภัยของข้อมูลที่สำคัญ
ค่าที่ถูกฮาร์ดโค้ดคือตัวเลข สตริง หรือการตั้งค่าใดๆ ที่เขียนลงในโค้ดโดยตรงแทนที่จะโหลดจากการกำหนดค่า ตัวอย่างเช่น หากนักพัฒนาเขียน `connectionTimeout = 30` ภายในคลาสการเชื่อมต่อฐานข้อมูล — นั่นคือฮาร์ดโค้ด หากเขาอ่านค่าระยะเวลารอจากตัวแปรสภาพแวดล้อมหรือไฟล์กำหนดค่า — นั่นคือวิธีการที่ถูกต้อง
คำว่าฮาร์ดโค้ดมาจากคำภาษาอังกฤษ hard code ที่แปลว่า “โค้ดที่แข็ง” ในสภาพแวดล้อมที่พูดภาษาไทย ก็ใช้คำว่า “การเขียนโค้ดแบบตายตัว” หรือ “ค่าคงที่” เช่นกัน แตกต่างจากการกำหนดค่าที่ยืดหยุ่น ฮาร์ดโค้ดถูก “เย็บ” ไว้ในไฟล์ปฏิบัติการอย่างแท้จริง และไม่สามารถเปลี่ยนแปลงได้หากไม่สร้างใหม่
ฮาร์ดโค้ดสร้างปัญหามากมายในระยะยาว ปัญหาแรกและชัดเจนที่สุดคือไม่สามารถเปลี่ยนพฤติกรรมของแอปพลิเคชันได้โดยไม่แก้ไขซอร์สโค้ด ปัญหาที่สองคือความเสี่ยงของการรั่วไหลของข้อมูลที่เป็นความลับ ปัญหาที่สามคือความซับซ้อนของการทดสอบ โดยเฉพาะการทดสอบหน่วยและการทดสอบการทำงานร่วมกัน
ในAgileและDevOps ซึ่งต้องการการปรับใช้อย่างรวดเร็วในสภาพแวดล้อมต่างๆ — การพัฒนา การทดสอบ การผลิต — ฮาร์ดโค้ดกลายเป็นอุปสรรคที่ข้ามไม่ได้ ทีมต้องแก้ไขโค้ดก่อนการปรับใช้แต่ละครั้งหรือใช้แพชแบบ manually ซึ่งขัดต่อหลักการของการส่งมอบอย่างต่อเนื่อง
การศึกษาของมหาวิทยาลัยเคมบริดจ์ (2023) แสดงให้เห็นว่าโครงการที่มีระดับฮาร์ดโค้ดสูงมีข้อบกพร่องในการเผยแพร่มากกว่า 47% และใช้เวลาในการเปลี่ยนแปลงมากกว่า 2.3 เท่า สิ่งนี้ยืนยันว่าต้นทุนการบำรุงรักษาโค้ดที่ถูกฮาร์ดโค้ดสูงกว่าการประหยัดเวลาในระยะเริ่มต้นของการพัฒนาอย่างมีนัยสำคัญ
แอปพลิเคชันที่มีพารามิเตอร์ถูกฮาร์ดโค้ดนั้นยากที่จะปรับให้เข้ากับแพลตฟอร์มต่างๆ ตัวอย่างเช่น เส้นทางไฟล์ `C:\Users\admin\data.txt` จะไม่ทำงานบนเซิร์ฟเวอร์ Linux และขนาดฟอนต์ 14pt อาจดูแตกต่างกันบนอุปกรณ์ที่มีความหนาแน่นของพิกเซลต่างกัน
เมื่อฮาร์ดโค้ดกระจายอยู่ทั่วโครงการ นักพัฒนาต้องค้นหาแต่ละค่าแบบ manually โดยใช้ grep หรือการค้นหาของ IDE ซึ่งทำให้การพัฒนาช้าลง เพิ่มโอกาสในการพลาดค่าที่จำเป็น และเปิดทางให้เกิดบั๊ก ในขณะเดียวกัน สมาชิกทีมใหม่ใช้เวลามากขึ้นอย่างมากในการทำความเข้าใจ “เลขมหัศจรรย์” และสตริง
รหัสผ่านและข้อมูลรับรองเป็นประเภทฮาร์ดโค้ดที่อันตรายที่สุด นักพัฒนามักบันทึกรหัสผ่านฐานข้อมูล คีย์ API ของบริการบุคคลที่สาม และโทเค็นการอนุญาตลงในโค้ดโดยตรงเพื่อความสะดวกในการพัฒนาท้องถิ่น แต่ลืมแยกออกก่อน commit สิ่งนี้นำไปสู่การรั่วไหลในที่เก็บสาธารณะ
URLและจุดสิ้นสุดของบริการภายนอกก็มักตกเป็นเหยื่อของฮาร์ดโค้ดเช่นกัน เมื่อเปลี่ยนโฮสติ้งหรือเวอร์ชัน API นักพัฒนาต้องอัปเดต URL ในหลายสิบแห่ง หากที่อยู่ถูกฮาร์ดโค้ดในหลายโมดูล ลิงก์บางส่วนจะเก่าและแอปพลิเคชันทำงานไม่ถูกต้อง
เลขมหัศจรรย์ — ค่าคงที่ที่เป็นตัวเลขโดยไม่มีคำอธิบาย ตัวอย่างเช่น `price * 0.85` แทน `price * DISCOUNT_RATE` ผู้อ่านโค้ดไม่เข้าใจว่า 0.85 หมายถึงอะไร นี่คือตัวอย่างคลาสสิกของฮาร์ดโค้ดที่ Martin Fowler อธิบายไว้ในหนังสือ “Refactoring” (1999)
| ประเภทของฮาร์ดโค้ด | ตัวอย่าง | วิธีการที่ถูกต้อง |
|---|---|---|
| ข้อมูลรับรอง | `password = “qwerty123”` | ตัวแปรสภาพแวดล้อม |
| URL เซิร์ฟเวอร์ | `url = “https://old-server.com/api”` | ไฟล์กำหนดค่า |
| เวลาหมดอายุ | `setTimeout(5000)` | พารามิเตอร์กำหนดค่า |
| ขนาด UI | `width = 320` | การคำนวณแบบตอบสนอง |
| เส้นทางไฟล์ | `“./data/output.txt”` | อาร์กิวเมนต์บรรทัดคำสั่ง |
ลิเทอรัลสตริงที่ซ้ำกันในส่วนต่างๆ ของโปรแกรมเป็นอีกประเภทหนึ่งของฮาร์ดโค้ดที่พบบ่อย ตัวอย่างเช่น คีย์พจนานุกรม ส่วนหัว HTTP ชื่อมุมมองในแอปพลิเคชัน iOS หากสตริงเปลี่ยนในที่หนึ่งแต่ยังคงเดิมในอีกที่หนึ่ง แอปพลิเคชันจะเสีย วิธีแก้คือแยกสตริงออกเป็นค่าคงที่หรือไฟล์แปลภาษา
โหมดแอปพลิเคชัน (ดีบัก/รีลีส) การตั้งค่าการบันทึก ที่อยู่เซิร์ฟเวอร์ SMTP — พารามิเตอร์ทั้งหมดนี้ควรอยู่ภายนอก หากถูกฮาร์ดโค้ด เมื่อย้ายไปยังเซิร์ฟเวอร์อื่น แอปพลิเคชันอาจไม่เริ่มทำงานหรือเริ่มมีพฤติกรรมที่คาดเดาไม่ได้
รหัสผ่านและคีย์ที่ถูกฮาร์ดโค้ดเป็นภัยคุกคามโดยตรงต่อความปลอดภัยของแอปพลิเคชัน หากผู้โจมตีสามารถเข้าถึงซอร์สโค้ด (ผ่านการรั่วไหลของที่เก็บ การคุกคามจากภายใน หรือการดีคอมไพล์) เขาจะสามารถเข้าถึงทรัพยากรที่ได้รับการป้องกันทั้งหมดได้ทันที ในปี 2023 GitHub ค้นพบการรั่วไหลของความลับมากกว่า 12 ล้านครั้งในที่เก็บสาธารณะ
มาตรฐานOWASP (โครงการความปลอดภัยแอปพลิเคชันเว็บแบบเปิด) รวมข้อมูลรับรองที่ถูกฮาร์ดโค้ดในหมวด A04:2021 — การออกแบบที่ไม่ปลอดภัย OWASP แนะนำว่าไม่ควรเก็บรหัสผ่าน โทเค็น หรือคีย์ในซอร์สโค้ด ให้ใช้บริการจัดการความลับเฉพาะทางแทน: HashiCorp Vault, AWS Secrets Manager หรือ Azure Key Vault
การตรวจสอบความปลอดภัยที่ดำเนินการโดยPositive Technologies (2024) แสดงให้เห็นว่า 78% ของแอปพลิเคชันมือถือที่ทดสอบมีคีย์หรือโทเค็นที่ถูกฮาร์ดโค้ดอย่างน้อยหนึ่งรายการ สำหรับแอปพลิเคชันเว็บ ตัวเลขนี้คือ 62% ช่องโหว่ส่วนใหญ่สามารถกำจัดได้โดยเพียงแยกข้อมูลออกเป็นไฟล์กำหนดค่า
# hardcoded secrets — unsafe
password = "supersecret123"
api_key = "sk-abc123def456"
# safe approach — read from env vars
import os
password = os.getenv("DB_PASSWORD")
api_key = os.getenv("API_KEY")
Gitเก็บประวัติคอมมิตทั้งหมดไว้ หากรหัสผ่านที่ถูกฮาร์ดโค้ดเข้าไปในที่เก็บ มันจะยังคงอยู่ในประวัติแม้หลังจากถูกลบออกจากเวอร์ชันปัจจุบันแล้วก็ตาม เครื่องมืออย่าง git-secrets และ truffleHog ช่วยตรวจจับการรั่วไหลดังกล่าว แต่ควรป้องกันตั้งแต่ขั้นตอนการตรวจสอบโค้ดจะดีกว่า
มาตรฐาน PCI DSS, GDPR และ HIPAA ห้ามเก็บข้อมูลที่เป็นความลับในซอร์สโค้ดโดยตรง การใช้ฮาร์ดโค้ดอาจนำไปสู่ผลทางกฎหมายและค่าปรับ โดยเฉพาะในภาคการเงินและการแพทย์
ขั้นตอนแรกในการกำจัดฮาร์ดโค้ดคือการตระหนักรู้ในระดับทีม การตรวจสอบโค้ดควรรวมการตรวจสอบค่าที่ถูกฮาร์ดโค้ดด้วย ตั้งค่า linter หรือตัววิเคราะห์แบบ static ที่จะเน้นฮาร์ดโค้ดที่อาจเกิดขึ้น สำหรับ TypeScript, ESLint ด้วยกฎ no-hardcoded-credentials ทำงานได้ดี สำหรับ Python, Bandit
ขั้นตอนที่สองคือการใช้รูปแบบ Configuration as Code พารามิเตอร์ทั้งหมดที่อาจแตกต่างกันในสภาพแวดล้อมต่างๆ ควรเก็บในตัวแปรสภาพแวดล้อมหรือไฟล์กำหนดค่า ไลบรารีอย่าง dotenv (Node.js), python-decouple (Python) หรือ Spring Cloud Config (Java) ทำให้วิธีการนี้เป็นมาตรฐาน
ขั้นตอนที่สามคือการใช้บริการจัดการการกำหนดค่า: Consul, etcd, Zookeeper สำหรับโครงการคลาวด์ AWS Parameter Store, Google Cloud Secret Manager หรือ Azure App Configuration เหมาะสม ในสถาปัตยกรรมไมโครเซอร์วิส การจัดการการกำหนดค่าแบบรวมศูนย์มีความสำคัญอย่างยิ่ง
จัดทำเอกสารพารามิเตอร์การกำหนดค่าแต่ละรายการ: วัตถุประสงค์ ค่าที่อนุญาต ค่าเริ่มต้น ใช้การตรวจสอบ schema สำหรับการกำหนดค่า — ซึ่งช่วยตรวจจับข้อผิดพลาดเมื่อเริ่มต้นแอปพลิเคชัน สร้างไฟล์ .env.example พร้อมตัวแปรที่จำเป็นทั้งหมด แต่ไม่มีค่าจริง
ลองพิจารณาตัวอย่างที่เป็นรูปธรรมใน JavaScript ก่อนการรีแฟคเตอร์ โค้ดมี URL และระยะเวลารอที่ถูกฮาร์ดโค้ด หลังการรีแฟคเตอร์ พารามิเตอร์ทั้งหมดถูกแยกออกเป็นการกำหนดค่า ทำให้โค้ดสามารถทดสอบได้ ยืดหยุ่น และปลอดภัย
// before refactoring — hardcoded values
const response = await fetch("https://api.example.com/v1/users", {
timeout: 5000,
headers: { "Authorization": "Bearer sk-abc" }
});
// after refactoring — config driven
const config = {
apiUrl: process.env.API_URL,
timeout: parseInt(process.env.API_TIMEOUT || "30000"),
authToken: process.env.AUTH_TOKEN
};
const response = await fetch(config.apiUrl, {
timeout: config.timeout,
headers: { "Authorization": "Bearer " + config.authToken }
});
ในJava ฮาร์ดโค้ดมักปรากฏในรูปแบบของสตริงการเชื่อมต่อฐานข้อมูล การใช้ Spring Boot กับ application.yml แก้ปัญหานี้: ไฟล์มีโปรไฟล์สำหรับสภาพแวดล้อมต่างๆ และโค้ดอ่านค่าผ่านคำอธิบายประกอบ @Value
// hardcoded — Java example
class DatabaseConnection {
private String url = "jdbc:mysql://localhost:3306/mydb";
private String user = "admin";
private String password = "pass123";
}
// proper config via Spring Boot
@Value("${db.url}")
private String url;
วิธีการต่อสู้กับฮาร์ดโค้ดขึ้นอยู่กับภาษาและระบบนิเวศ ในภาษาที่ถูกแปลความ (Python, JavaScript, Ruby) การกำหนดค่ามักเก็บในตัวแปรสภาพแวดล้อมหรือไฟล์ .env ในภาษาที่ถูกคอมไพล์ (Java, C#, Go) เก็บในไฟล์กำหนดค่า YAML, JSON, XML หรือทรัพยากรที่ฝังอยู่
ในPython ไลบรารี python-decouple เป็นที่นิยม — อ่านการกำหนดค่าจากไฟล์ .env และให้ getter แบบระบุชนิด ในGo ใช้ Viper — ไลบรารีที่มีประสิทธิภาพสำหรับทำงานกับการกำหนดค่าจากแหล่งต่างๆ ในSwift สำหรับการพัฒนา iOS การกำหนดค่าจะถูกแยกไปยัง Info.plist หรือไฟล์ Configuration แยกต่างหาก
เครื่องมือวิเคราะห์แบบ static เช่น SonarQube, ESLint, Pylint สามารถตรวจจับค่าที่ถูกฮาร์ดโค้ดโดยอัตโนมัติ SonarQube มีกฎในตัวสำหรับค้นหาเลขมหัศจรรย์และสตริงในโค้ดในภาษาต่างๆ การตั้งค่าการตรวจสอบดังกล่าวในไปป์ไลน์ CI/CD เป็นวิธีที่ดีที่สุดในการป้องกันไม่ให้ฮาร์ดโค้ดใหม่เกิดขึ้น
| ภาษา | วิธีการกำหนดค่า | ไลบรารียอดนิยม |
|---|---|---|
| JavaScript | .env + ตัวแปรสภาพแวดล้อม | dotenv |
| Python | .env + สภาพแวดล้อม | python-decouple |
| Java | application.yml/properties | Spring Cloud Config |
| Go | config.yaml + env | Viper |
| Swift | Configuration.xcconfig | Build Configuration |
ฮุค Git pre-commitสามารถเรียกสคริปต์ที่ตรวจสอบคอมมิตว่ามีความลับที่ถูกฮาร์ดโค้ดหรือไม่ เครื่องมือ git-secrets สแกนคอมมิตเพื่อหาค่าที่ตรงกับนิพจน์ปกติสำหรับรหัสผ่าน คีย์ และโทเค็น TruffleHog และ Gitleaks ไปไกลกว่านั้น — ตรวจสอบประวัติ git ทั้งหมดเพื่อหารั่วไหล
คำถามที่พบบ่อย
ตัวแปรเก็บค่าที่สามารถเปลี่ยนแปลงได้ระหว่างการทำงานของโปรแกรม ฮาร์ดโค้ดคือลิเทอรัลที่เขียนลงในเนื้อหาของฟังก์ชันหรือคลาสโดยตรง ซึ่งไม่ได้ตั้งใจให้เปลี่ยนแปลงโดยไม่แก้ไขซอร์สโค้ด ตัวอย่างเช่น `let port = 8080` ภายในเมธอดคือฮาร์ดโค้ด ในขณะที่ `let port = config.port` คือการใช้ตัวแปรที่ถูกต้อง
ในส่วนใหญ่ของกรณี — ใช่ อย่างไรก็ตาม มีข้อยกเว้น: ค่าที่รับประกันว่าจะไม่เปลี่ยนแปลงตลอดอายุของแอปพลิเคชัน ตัวอย่างเช่น ค่าคงที่ทางคณิตศาสตร์ (π = 3.14159) หรือค่าคงที่ทางฟิสิกส์ แต่ถึงแม้ค่าเหล่านี้ก็ควรกำหนดเป็นค่าคงที่ที่มีชื่อเพื่อให้ชัดเจนว่าตัวเลขหมายถึงอะไร
ใช้ตัววิเคราะห์โค้ดแบบ static: SonarQube, ESLint ด้วยกฎ no-magic-numbers, Pylint ด้วย const-naming-style สำหรับค้นหาความลับ — git-secrets, truffleHog หรือ Gitleaks นิพจน์ปกติสำหรับค้นหา: รหัสหลังจาก `password =`, URL ที่มี http/https, ค่าคงที่ตัวเลขที่ไม่มีชื่อชัดเจน การตรวจสอบแบบ manual ผ่าน grep หรือการค้นหาใน IDE ก็ช่วยได้
เลขมหัศจรรย์คือลิเทอรัลที่เป็นตัวเลขในโค้ดโดยไม่มีคำอธิบายความหมาย ตัวอย่างเช่น `if (age > 18)` — เลข 18 เข้าใจได้ แต่ `if (score > 0.85)` — ไม่เข้าใจ อันตรายคือเมื่อเปลี่ยนเลขดังกล่าว นักพัฒนาอาจพลาดหนึ่งในตำแหน่งที่มันถูกใช้ ผลลัพธ์คือตรรกะของโปรแกรมเสียและบั๊กนั้นยากที่จะติดตาม
ไม่ การกำหนดค่าได้มากเกินไปทำให้โค้ดซับซ้อน กฎทอง: แยกสิ่งที่อาจเปลี่ยนแปลงเมื่อสภาพแวดล้อมหรือข้อกำหนดเปลี่ยนไป ค่าคงที่ภายในที่ไม่เปลี่ยนแปลงเป็นปี (เช่น ชื่อเมธอด HTTP มาตรฐาน) สามารถอยู่ในโค้ดได้ ปฏิบัติตามหลักการ YAGNI — อย่าเพิ่มการกำหนดค่า “เผื่อไว้”
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม