Boilerplate trong phát triển ứng dụng: nó là gì, ví dụ và cách giảm thiểu

Tác giả: IT Sectr Đã đăng: 2026-07-26 Thời gian đọc: 10 phút

Boilerplate là mã mẫu mà các nhà phát triển viết với những thay đổi tối thiểu trong mỗi module hoặc dự án mới. Nó không chứa logic kinh doanh độc đáo, mà chỉ đơn giản chuẩn bị cơ sở hạ tầng: cấu hình, import thư viện, trình xử lý tiêu chuẩn và các lớp DTO. Theo CodeScene Engineering Productivity Report (2025), boilerplate chiếm từ 20 đến 40 phần trăm tổng số mã trong một ứng dụng thương mại điển hình. Vấn đề chính của mã này không phải là nó lặp lại, mà là mỗi lần lặp lại là một điểm lỗi: lỗi trong một bản sao không đồng bộ với các bản khác và các lỗi nhân lên trong dự án. Tự động hóa việc tạo boilerplate thông qua sinh mã, chú thích và macro là một trong những cách hiệu quả nhất để tăng tốc phát triển mà không mất chất lượng.

Những điểm chính

  • Boilerplate — mã mẫu lặp lại từ module này sang module khác mà không thay đổi logic kinh doanh.
  • Nguồn chính: cấu hình DI, lớp DTO, màn hình biểu mẫu, yêu cầu mạng và ánh xạ ORM.
  • Boilerplate làm chậm phát triển và tăng số lượng lỗi khi sao chép.
  • Công cụ giảm thiểu: sinh mã, chú thích (Lombok, lớp Data), macro và trình tạo màn hình.
  • Mục tiêu không phải là loại bỏ hoàn toàn boilerplate, mà là tự động hóa việc tạo và đồng bộ hóa nó.

Boilerplate là gì?

Mã boilerplate là những đoạn mã nguồn lặp lại trong các phần khác nhau của dự án với những biến thể tối thiểu. Thuật ngữ này xuất phát từ ngành in ấn, nơi boilerplate chỉ các khối văn bản được viết sẵn cho báo chí không cần viết lại. Trong lập trình, đó là bất kỳ mã nào bạn buộc phải viết đi viết lại để đáp ứng yêu cầu của framework, ngôn ngữ hoặc kiến trúc.

Boilerplate không phải là nợ kỹ thuật theo nghĩa cổ điển — nó không chứa lỗi và không vi phạm các nguyên tắc SOLID. Tuy nhiên, nó làm tăng khối lượng mã cần bảo trì, kiểm thử và đọc. Mỗi dòng boilerplate là một vị trí tiềm ẩn cho lỗi chính tả mà trình biên dịch không phải lúc nào cũng phát hiện được.

Theo báo cáo JetBrains Developer Ecosystem (2025), 67 phần trăm nhà phát triển coi boilerplate là nguyên nhân chính làm giảm năng suất. Trong phát triển di động, con số này cao hơn: các dự án Android bằng Java chứa một lượng đáng kể mã mẫu cho findViewById, Intents, bộ điều hợp RecyclerView và ContentProvider. Kotlin và Swift đã giải quyết một số vấn đề này bằng phương tiện cú pháp, nhưng boilerplate vẫn chưa biến mất hoàn toàn.

Khi thiết kế kiến trúc, hãy cố gắng chọn các giải pháp giảm thiểu mã mẫu. Ví dụ, thay vì viết thủ công triển khai Parcelable, hãy sử dụng @Parcelize trong Kotlin. Thay vì factory cho ViewModel — Hilt với @HiltViewModel. Mỗi tối ưu hóa như vậy tiết kiệm hàng giờ phát triển ở quy mô dự án.

Ví dụ về boilerplate trong dự án di động

Ví dụ dễ nhận biết nhất về boilerplate trong phát triển Android là RecyclerView.Adapter. Trước Kotlin và ViewBinding, mỗi bộ điều hợp yêu cầu khoảng 80–100 dòng mã mẫu: onCreateViewHolder, onBindViewHolder, getItemCount, lớp ViewHolder nội bộ, hàm tạo, liên kết trường. Với ViewBinding, mã đã giảm nhưng không hoàn toàn biến mất.

Boilerplate bộ điều hợp không tối ưu hóa

kotlin
class UserAdapter(
    private val users: List<User>
) : RecyclerView.Adapter<UserAdapter.ViewHolder>() {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {
        val view = LayoutInflater
            .from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(
        holder: ViewHolder,
        position: Int
    ) {
        holder.bind(users[position])
    }

    override fun getItemCount(): Int = users.size

    class ViewHolder(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        fun bind(user: User) {
            Glide.with(itemView)
                .load(user.avatarUrl)
                .into(itemView.avatar)
        }
    }
}

Một ví dụ minh họa khác là ánh xạ JSON trong Java không có thư viện. Phân tích thủ công phản hồi API yêu cầu viết hàng chục phương thức, mỗi phương thức kiểm tra sự tồn tại của khóa, lấy giá trị và gán cho trường. Với các thư viện như Gson, Moshi hoặc Kotlin Serialization — chỉ cần một chú thích @Serializable.

Trong phát triển iOS, boilerplate cổ điển là triển khai CodingKeyDecodable cho mỗi phản hồi API, đặc biệt khi các khóa JSON khác với tên thuộc tính camelCase. Mặc dù có tự động tạo Codable, việc liệt kê thủ công CodingKeys vẫn là nguồn gốc của mã mẫu.

Sử dụng sinh mã để tạo boilerplate tại thời điểm biên dịch. Trên Android — Annotation Processing (KSP) cho Room, Dagger, Moshi. Trên iOS — Sourcery cho Codable và AutoMockable. Mỗi giờ dành cho việc thiết lập sinh mã, bạn tiết kiệm được nhiều ngày sao chép thủ công.

Tại sao mã mẫu có hại

Boilerplate gây hại cho dự án theo ba cách: làm chậm việc viết chức năng mới, làm phức tạp việc đọc mã hiện có và tạo ra các điểm mất đồng bộ khi thay đổi.

Sự chậm lại trong phát triển là rõ ràng: nhà phát triển dành thời gian viết mã không chứa logic kinh doanh. Thay vì triển khai một tính năng mới (ví dụ: thêm trường vào hồ sơ người dùng), họ viết di chuyển DB, lớp DTO, ánh xạ đến thực thể miền, màn hình với trường nhập liệu, xác thực và kiểm thử cho mỗi lớp. Phần lớn công việc này là cơ học.

Mất đồng bộ là một vấn đề nguy hiểm hơn. Khi cấu trúc dữ liệu thay đổi ở một nơi (ví dụ: một trường được thêm vào phản hồi API), nhà phát triển phải cập nhật DTO, bộ ánh xạ, mô hình, màn hình và kiểm thử. Nếu bỏ sót một nơi, ứng dụng biên dịch được nhưng gặp sự cố khi chạy hoặc — tệ hơn — hiển thị dữ liệu sai mà không có lỗi. Càng nhiều lớp boilerplate, khả năng mất đồng bộ càng cao.

Phân tích dự án để tìm các mẫu lặp lại. Nếu bạn thấy ba lớp giống hệt nhau với tên khác nhau — đó là ứng viên cho sinh mã. Hãy đưa sinh mã vào như một phần của giải pháp kiến trúc, không phải như một tối ưu hóa một lần. Nó mang lại hiệu quả với mỗi module mới.

Sinh mã để tự động hóa boilerplate

Sinh mã là cách đáng tin cậy nhất để chống lại boilerplate. Thay vì viết thủ công mã mẫu, nhà phát triển mô tả siêu dữ liệu (chú thích, lược đồ, cấu hình) và trình tạo tạo mã sẵn sàng tại thời điểm biên dịch.

Trong hệ sinh thái Android, công cụ sinh mã tiêu chuẩn là KSP (Kotlin Symbol Processing). Nó thay thế KAPT cũ và hoạt động nhanh hơn nhờ truy cập trực tiếp vào AST Kotlin mà không tạo ra các Java stub. KSP được sử dụng bởi Room (tạo triển khai DAO), Moshi (tạo JsonAdapter), Glide (tạo lớp tải mục tiêu) và Dagger (tạo đồ thị DI).

Sinh thực thể Room với KSP

kotlin
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: Long,
    @ColumnInfo(name = "full_name") val name: String,
    @ColumnInfo(name = "avatar_url") val avatarUrl: String
)

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getById(@Param("id") id: Long): UserEntity?
}

Trong phát triển iOS, vai trò sinh mã do Sourcery đảm nhận — một công cụ xử lý các mẫu Stencil và tạo mã Swift dựa trên các chú thích trong bình luận. Các kịch bản điển hình: AutoMockable (tạo mock cho kiểm thử), AutoCodable (triển khai Decodable không cần CodingKeys), AutoEquatable và AutoLenses.

Đối với các dự án Flutter, boilerplate được giảm bởi các trình tạo qua build_runner: json_serializable cho ánh xạ JSON, freezed cho các mô hình bất biến với copyWith, retrofit_generator cho máy khách API và injectable_generator cho DI. Mỗi trình tạo này biến 10–20 dòng chú thích thành hàng trăm dòng mã sẵn sàng.

Giảm thiểu qua chú thích và macro

Chú thíchmacro là một cách khai báo để chỉ cho trình biên dịch hoặc bộ tiền xử lý biết mã nào cần tạo. Nhà phát triển không viết triển khai, mà chỉ đánh dấu ý định và trình tạo biến đánh dấu thành mã sẵn sàng.

Ví dụ nổi bật nhất là Lombok trong Java (lịch sử) và data class của Kotlin. Data class trong Kotlin tự động tạo equals, hashCode, toString, componentN và copy — trong Java, điều này yêu cầu khoảng 80 dòng mã viết tay hoặc sử dụng Lombok với @Data. Kotlin đã giải quyết vấn đề ở cấp độ ngôn ngữ, làm cho boilerplate trở nên ngầm định.

Trong Swift, vai trò tương tự được thực hiện bởi macro (Swift Macros, được giới thiệu trong Swift 5.9). Thay vì viết thủ công triển khai Codable, nhà phát triển đánh dấu cấu trúc bằng @Codable — và trình biên dịch tạo mã cần thiết. Các macro tích hợp khác: @Observable (trạng thái có thể quan sát), @ResultBuilder (trình xây dựng kết quả) và @MainActor (điều phối đến luồng chính).

swift
@Codable
struct UserProfile {
    let id: Int
    let displayName: String
    let avatarURL: URL
    let bio: String?
}

// @Codable macro generates:
// extension UserProfile: Codable { }
// private enum CodingKeys: String, CodingKey {
//     case id, displayName, avatarURL, bio
// }

Khi lựa chọn giữa sinh mã và macro, hãy ưu tiên macro nếu ngôn ngữ hỗ trợ chúng. Macro hoạt động ở cấp độ trình biên dịch, không yêu cầu cấu hình tập lệnh xây dựng, không làm chậm biên dịch (không giống như Annotation Processing) và luôn đồng bộ với mã nguồn. Nếu macro không khả dụng — hãy sử dụng trình tạo bên ngoài qua KSP, Sourcery hoặc build_runner.

Thực hành giảm thiểu theo ngôn ngữ

Mỗi ngôn ngữ và nền tảng cung cấp các công cụ riêng để giảm thiểu boilerplate. Dưới đây là các thực hành cụ thể cho các stack phát triển di động chính.

Nền tảngCông cụ / Kỹ thuậtThay thế cho
Android / Kotlindata classequals, hashCode, toString, copy, componentN
Android / Kotlin@ParcelizeTriển khai Parcelable
Android / KotlinViewBinding / DataBindingfindViewById, ButterKnife
iOS / SwiftCodable + macroPhân tích JSON thủ công, CodingKeys
iOS / SwiftSourceryAutoMockable, AutoEquatable, AutoLenses
Flutter / Dartfreezed + json_serializablecopyWith, lớp sealed, equals/hashCode, JSON
Flutter / Dartretrofit_generatorMáy khách API với yêu cầu và phản hồi có kiểu

Đối với frontend web (React Native / TypeScript), công cụ chính là sinh kiểu từ đặc tả OpenAPI (openapi-typescript, swagger-codegen). Mỗi điểm cuối tự động nhận được yêu cầu và phản hồi có kiểu — nhà phát triển không cần mô tả thủ công các giao diện cho hàng trăm lời gọi API.

Hãy đưa sinh mã vào ở giai đoạn đầu của dự án. Di chuyển một dự án hiện có sang trình tạo khó hơn là thiết kế với chúng từ đầu. Nếu dự án đã được viết — hãy bắt đầu từ điểm đau nhất: Java → Kotlin (data class), bộ điều hợp thủ công → ListAdapter với DiffUtil, ánh xạ JSON thủ công → Moshi / Kotlin Serialization.

Câu hỏi thường gặp

Boilerplate khác với nợ kỹ thuật như thế nào?

Boilerplate không phải là nợ, mà là sự dư thừa: mã đúng nhưng có quá nhiều. Nợ kỹ thuật là một quyết định thỏa hiệp có ý thức sẽ phải sửa sau. Boilerplate không yêu cầu sửa chữa — nó yêu cầu tự động hóa.

Có boilerplate có phải luôn xấu không?

Không, trong các dự án nhỏ, boilerplate có thể được biện minh bởi sự đơn giản: nó có thể thấy ngay và dễ thay đổi. Vấn đề phát sinh ở quy mô lớn — khi có hơn mười module tương tự, sao chép thủ công không còn hiệu quả và đã đến lúc đưa sinh mã vào.

Loại boilerplate nào không thể tự động hóa?

Mã phụ thuộc vào các dịch vụ bên ngoài với logic không chuẩn (SDK tùy chỉnh, giao thức độc quyền) rất khó tạo. Trong những trường hợp như vậy, boilerplate được viết thủ công nhưng được cô lập vào các module riêng biệt để giảm thiểu sự lan rộng trong dự án.

Có nên sử dụng Lombok trong các dự án Java mới không?

Đối với các dự án mới, tốt hơn nên chuyển thẳng sang Kotlin, nơi data class giải quyết các tác vụ tương tự ở cấp độ ngôn ngữ. Nếu dự án vẫn ở Java — Lombok vẫn là tiêu chuẩn thực tế, nhưng hãy lưu ý rằng nó yêu cầu plugin IDE và có thể xung đột với các phiên bản Java mới.

Sinh mã có làm tăng thời gian biên dịch không?

Có, sinh mã thêm thời gian vào quá trình biên dịch. KSP nhanh hơn KAPT nhưng vẫn thêm vài giây hoặc vài phút vào một bản dựng hoàn chỉnh. Tối ưu hóa: sử dụng bản dựng tăng dần và lưu kết quả sinh mã giữa các lần xây dựng.

Tổng kết

  • Boilerplate — mã mẫu lặp lại trong mọi module và không chứa logic kinh doanh độc đáo.
  • Nguồn chính: lớp DTO, bộ ánh xạ, yêu cầu mạng, bộ điều hợp, cấu hình DI và thực thể ORM.
  • Boilerplate làm chậm phát triển, tăng nguy cơ mất đồng bộ và làm phức tạp việc đọc mã.
  • Phương pháp chính để chống lại nó — sinh mã qua KSP, Sourcery, build_runner hoặc openapi-typescript.
  • Chú thích và macro (data class, Codable, @Parcelize, freezed) tự động hóa các mẫu phổ biến nhất.
  • Chọn sinh mã ở giai đoạn thiết kế kiến trúc, không phải như một tối ưu hóa muộn.
  • Mỗi ngôn ngữ có công cụ riêng: Kotlin data class, Swift macro, Dart freezed — hãy sử dụng chúng theo mặc định.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm