Tôi nên cấu trúc các dự án phức tạp trong C như thế nào? [đóng cửa]


79

Tôi có nhiều hơn một chút so với các kỹ năng C cấp độ người mới bắt đầu và muốn biết liệu có bất kỳ "tiêu chuẩn" thực tế nào để cấu trúc một ứng dụng hơi phức tạp trong C. Ngay cả những ứng dụng dựa trên GUI.

Tôi đã luôn sử dụng mô hình OO trong Java và PHP và bây giờ tôi muốn học C, tôi sợ rằng tôi có thể cấu trúc các ứng dụng của mình sai cách. Tôi lúng túng không biết nên tuân theo các nguyên tắc nào để có tính mô-đun, tách rời và khô khan với ngôn ngữ thủ tục.

Bạn có bất kỳ bài đọc nào để đề xuất? Tôi không thể tìm thấy bất kỳ khung ứng dụng nào cho C, ngay cả khi tôi không sử dụng các khung công tác, tôi luôn tìm thấy những ý tưởng hay bằng cách duyệt qua mã của chúng.


3
Triết lý Unix khá hữu ích để tổ chức các dự án lớn: http://www.faqs.org/docs/artu/ch01s06.html
newDelete

Câu trả lời:


51

Điều quan trọng là tính mô-đun. Điều này dễ dàng hơn để thiết kế, thực hiện, biên dịch và bảo trì.

  • Xác định các mô-đun trong ứng dụng của bạn, chẳng hạn như các lớp trong ứng dụng OO.
  • Giao diện và triển khai riêng biệt cho từng mô-đun, chỉ đưa vào giao diện những gì cần thiết cho các mô-đun khác. Hãy nhớ rằng không có không gian tên trong C, vì vậy bạn phải làm cho mọi thứ trong giao diện của bạn là duy nhất (ví dụ: với tiền tố).
  • Ẩn các biến toàn cục trong việc triển khai và sử dụng các hàm truy cập để đọc / ghi.
  • Đừng nghĩ về mặt kế thừa, mà là về mặt cấu tạo. Theo nguyên tắc chung, đừng cố bắt chước C ++ trong C, điều này sẽ rất khó đọc và duy trì.

Nếu bạn có thời gian để tìm hiểu, hãy xem cách một ứng dụng Ada được cấu trúc, với bắt buộc package(giao diện mô-đun) và package body(triển khai mô-đun).

Đây là để viết mã.

Để duy trì (hãy nhớ rằng bạn viết mã một lần, nhưng bạn duy trì nhiều lần), tôi khuyên bạn nên ghi lại mã của mình; Doxygen là một lựa chọn tốt cho tôi. Tôi cũng khuyên bạn nên xây dựng một bộ thử nghiệm hồi quy mạnh, cho phép bạn cấu trúc lại.


30

Đó là một quan niệm sai lầm phổ biến rằng các kỹ thuật OO không thể được áp dụng trong C. Hầu hết đều có thể - chỉ là chúng hơi khó sử dụng hơn so với các ngôn ngữ có cú pháp dành riêng cho công việc.

Một trong những nền tảng của thiết kế hệ thống mạnh mẽ là sự đóng gói của một triển khai đằng sau một giao diện. FILE*và các hàm hoạt động với nó ( fopen(), fread()v.v.) là một ví dụ điển hình về cách đóng gói có thể được áp dụng trong C để thiết lập giao diện. (Tất nhiên, vì C thiếu các chỉ định truy cập nên bạn không thể bắt buộc không ai có thể nhìn vào bên trong a struct FILE, mà chỉ một kẻ bạo dâm mới làm như vậy.)

Nếu cần, hành vi đa hình có thể có trong C bằng cách sử dụng các bảng con trỏ hàm. Có, cú pháp là xấu nhưng hiệu quả giống như các hàm ảo:


11
Một C coder tinh khiết sẽ bị lạc đọc mã này ...
mouviciel

15
@mouviciel: Rác rưởi! Hầu hết các lập trình viên C hiểu các con trỏ hàm (hoặc ít nhất là họ nên làm như vậy), và không có gì thực sự xảy ra ở đây. Ít nhất trên Windows, trình điều khiển thiết bị và đối tượng COM đều cung cấp chức năng của chúng theo cách này.
j_random_hacker

8
Quan điểm của tôi không phải là về sự kém cỏi, mà là về sự phức tạp không cần thiết. Con trỏ hàm là phổ biến cho một C coder (ví dụ: gọi lại), kế thừa thì không. Tôi thích một lập trình viên C ++ viết mã trong C sử dụng thời gian của mình để học C hơn là xây dựng các lớp C ++ giả. Điều đó nói rằng, cách tiếp cận của bạn có thể hữu ích trong một số trường hợp.
mouviciel

3
Trên thực tế, bạn thậm chí có thể mô phỏng các chỉ định truy cập bằng cách sử dụng thành ngữ pimpl C ++. Nếu các thành viên riêng tư của một loại được gói gọn trong một loại chỉ hiển thị trong quá trình triển khai (còn gọi là "tệp .c"), người dùng giao diện sẽ gặp khó khăn trong việc thay đổi chúng (tất nhiên, họ có thể ghi rác vào con trỏ pimpl, nếu họ muốn cố tình vặn bạn , nhưng bạn có thể làm tương tự trong C ++).
bitmask

2
Downvoter: quan tâm đến bình luận?
j_random_hacker 17/10/13

15

Tất cả các câu trả lời tốt.

Tôi sẽ chỉ thêm "giảm thiểu cấu trúc dữ liệu". Điều này thậm chí có thể dễ dàng hơn trong C, bởi vì nếu C ++ là "C với các lớp", OOP đang cố gắng khuyến khích bạn lấy mọi danh từ / động từ trong đầu và biến nó thành một lớp / phương thức. Điều đó có thể rất lãng phí.

Ví dụ: giả sử bạn có một loạt các kết quả đọc nhiệt độ tại các thời điểm và bạn muốn hiển thị chúng dưới dạng biểu đồ đường trong Windows. Windows có một thông báo PAINT và khi bạn nhận được nó, bạn có thể lặp qua mảng thực hiện các chức năng LineTo, chia tỷ lệ dữ liệu khi bạn chuyển nó sang tọa độ pixel.

Những gì tôi đã thấy quá nhiều lần là, vì biểu đồ bao gồm các điểm và đường, nên mọi người sẽ xây dựng một cấu trúc dữ liệu bao gồm các đối tượng điểm và đối tượng đường, mỗi đối tượng có khả năng DrawMyself, và sau đó làm cho điều đó trở nên bền bỉ, trên lý thuyết bằng cách nào đó "hiệu quả hơn", hoặc họ có thể, chỉ có thể, phải có khả năng di chuột qua các phần của biểu đồ và hiển thị dữ liệu ở dạng số, vì vậy họ xây dựng các phương thức vào các đối tượng để giải quyết vấn đề đó và tất nhiên, liên quan đến việc tạo và xóa nhiều đối tượng hơn.

Vì vậy, bạn kết thúc với một lượng lớn mã rất dễ đọc và chỉ dành 90% thời gian để quản lý các đối tượng.

Tất cả những điều này được thực hiện với danh nghĩa "thực hành lập trình tốt" và "hiệu quả".

Ít nhất trong C, cách đơn giản, hiệu quả sẽ rõ ràng hơn, và sự cám dỗ xây dựng kim tự tháp ít mạnh hơn.


1
Thích câu trả lời của bạn, và nó hoàn toàn đúng. OOP phải chết!
Jo So

@JoSo: Tôi sử dụng OOP, nhưng ở mức tối thiểu.
Mike Dunlavey

Đối với tôi "tối thiểu" (mặc dù tôi không biết điều đó có ý nghĩa gì với bạn) không thực sự được coi là OOP, là chương trình hướng đối tượng - các đối tượng theo mặc định.
Jo So

Tôi cũng sử dụng một số "OOP". Chủ yếu dành cho các mô-đun C của tôi, trong đó nhiều mô-đun có thói quen init () và exit ().
Jo So

11

Các tiêu chuẩn mã hóa GNU đã phát triển trong một vài thập kỷ. Sẽ là một ý kiến ​​hay nếu bạn đọc chúng, ngay cả khi bạn không theo dõi chúng đến tận lá thư. Suy nghĩ về những điểm được nêu ra trong đó giúp bạn có cơ sở vững chắc hơn về cách cấu trúc mã của riêng bạn.


4
Không phải ai cũng thích chúng, từ lxr.linux.no/linux+v2.6.29/Documentation/CodingStyle : "Trước hết, tôi khuyên bạn nên in ra một bản sao của các tiêu chuẩn mã hóa GNU và KHÔNG đọc nó. Hãy ghi chúng, đó là nghĩa cử cao đẹp ”. Tôi đã không đọc chúng trong nhiều năm, nhưng Linus có một số phản đối xác đáng.
hlovdal

1
@hlovdal: Tất nhiên không phải ai cũng thích bất kỳ tiêu chuẩn mã hóa cụ thể nào, đó là lý do tại sao có nhiều tiêu chuẩn cho các trường hợp sử dụng tương tự. Phần quan trọng là sự nhất quán của bạn trong các dự án của riêng bạn, rằng ít nhất một số tiêu chuẩn được tuân theo, thay vì sự không nhất quán trên thực tế.
JM Becker

4

Nếu bạn biết cách cấu trúc mã của mình bằng Java hoặc C ++, thì bạn có thể tuân theo các nguyên tắc tương tự với mã C. Sự khác biệt duy nhất là bạn không có trình biên dịch bên cạnh và bạn cần phải làm mọi thứ một cách thủ công cực kỳ cẩn thận.

Vì không có gói và lớp, bạn cần bắt đầu bằng cách thiết kế cẩn thận các mô-đun của mình. Cách tiếp cận phổ biến nhất là tạo một thư mục nguồn riêng biệt cho mỗi mô-đun. Bạn cần dựa vào các quy ước đặt tên để phân biệt mã giữa các mô-đun khác nhau. Ví dụ tiền tố tất cả các chức năng với tên của mô-đun.

Bạn không thể có các lớp với C, nhưng bạn có thể dễ dàng triển khai "Các kiểu dữ liệu trừu tượng". Bạn tạo tệp .C và .H cho mọi kiểu dữ liệu trừu tượng. Nếu muốn, bạn có thể có hai tệp tiêu đề, một tệp công khai và một tệp riêng tư. Ý tưởng là tất cả các cấu trúc, hằng số và chức năng cần được xuất sang tệp tiêu đề công khai.

Các công cụ của bạn cũng rất quan trọng. Một công cụ hữu ích cho C là lint , có thể giúp bạn tìm ra mùi hôi trong mã của mình. Một công cụ khác mà bạn có thể sử dụng là Doxygen, có thể giúp bạn tạo tài liệu .


4

Tính đóng gói luôn là chìa khóa để phát triển thành công, bất kể ngôn ngữ phát triển là gì.

Một mẹo mà tôi đã sử dụng để giúp đóng gói các phương thức "riêng tư" trong C là không đưa các nguyên mẫu của chúng vào tệp ".h".


3

Tôi muốn bạn kiểm tra mã của bất kỳ dự án nguồn mở C phổ biến nào, như ... hmm ... Linux kernel, hoặc Git; và xem cách họ tổ chức nó.


3

Quy tắc số cho ứng dụng phức tạp: nó phải dễ đọc.

Để làm cho ứng dụng phức tạp trở nên đơn giản hơn, tôi sử dụng Chia và chinh phục .


2

Tôi khuyên bạn nên đọc một cuốn sách giáo khoa C / C ++ như một bước đầu tiên. Ví dụ, C Primer Plus là một tài liệu tham khảo tốt. Xem qua các ví dụ sẽ cung cấp cho bạn và ý tưởng về cách ánh xạ java OO của bạn sang một ngôn ngữ thủ tục hơn như C.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.