8

Tôi thích sắp xếp mã của mình, vì vậy lý tưởng tôi muốn một lớp cho mỗi tệp hoặc khi tôi có các hàm không phải là thành viên, một hàm cho mỗi tệp.Tôi có nên đặt nhiều chức năng vào một tệp không? Hoặc, nhiều hay ít, một hàm cho mỗi tệp?

Lý do là:

  1. Khi tôi đọc mã tôi sẽ luôn luôn biết trong tập tin những gì tôi nên tìm một chức năng nhất định hoặc lớp học.

  2. Nếu nó là một lớp học hoặc phi thành viên chức năng cho mỗi tập tin tiêu đề, sau đó tôi sẽ không bao gồm cả một mớ hỗn độn khi tôi include một tập tin header.

  3. Nếu tôi thực hiện một thay đổi nhỏ trong một chức năng thì chỉ có chức năng đó sẽ phải được biên dịch lại.

Tuy nhiên, chia mọi thứ thành nhiều tiêu đề và nhiều tệp triển khai có thể làm chậm quá trình biên dịch. Trong dự án của tôi, hầu hết các chức năng truy cập một số lượng nhất định các chức năng thư viện khác theo khuôn mẫu. Vì vậy, mã sẽ được biên dịch hơn và hơn, một lần cho mỗi tập tin thực hiện. Biên soạn toàn bộ dự án của tôi hiện đang mất 45 phút hoặc lâu hơn trên một máy. Có khoảng 50 tệp đối tượng và mỗi tệp sử dụng cùng tiêu đề đắt tiền để biên dịch.

Có lẽ, nó là có thể chấp nhận để có một lớp (hoặc chức năng không thành viên) mỗi tiêu đề tập tin, nhưng việc đưa việc triển khai của nhiều hoặc tất cả các chức năng này vào một tập tin thực hiện, như trong ví dụ sau?

// foo.h 
void foo(int n); 

// bar.h 
void bar(double d); 

// foobar.cpp 
#include <vector> 
void foo(int n) { std::vector<int> v; ... } 
void bar(double d) { std::vector<int> w; ... } 

Một lần nữa, lợi thế sẽ là tôi có thể chỉ bao gồm chức năng foo hay chỉ là chức năng thanh, và biên soạn toàn bộ dự án sẽ nhanh hơn vì foobar.cppmột tập tin, do đó std::vector<int> (đó là chỉ là một ví dụ ở đây cho một số xây dựng đắt tiền khác để biên dịch templated) đã được biên soạn chỉ trong một lần, trái ngược với hai lần nếu tôi biên soạn một foo.cppbar.cpp riêng biệt. Tất nhiên, lý do của tôi (3) ở trên là không hợp lệ cho kịch bản này: Sau khi chỉ thay đổi foo() {...} Tôi phải biên dịch lại toàn bộ, tiềm năng lớn, tập tin foobar.cpp.

Tôi tò mò ý kiến ​​của bạn là gì!

+3

Haha, bạn Úc! Trong tiếng Anh Mỹ, bạn không "thích", nhưng "yêu" mọi thứ. Ngoài ra, mọi thứ thường "tuyệt vời" và "to lớn" thay vì chỉ "tốt" hoặc "tuyệt vời"! Nếu, bằng tiếng Anh Mỹ, bạn chỉ nói rằng bạn "thích" một cái gì đó, nó có nghĩa là bạn thực sự không thích nó! ;-) – Frank

+1

... nhưng cảm ơn vì nhận xét "tuyệt vời"! :-P – Frank

Trả lời

8

IMHO, bạn nên kết hợp các mục thành các nhóm lôgic và tạo các tệp dựa trên đó.

Khi tôi đang viết các hàm, thường có một nửa tá hoặc có liên quan chặt chẽ với nhau. Tôi có xu hướng đặt chúng lại với nhau trong một tiêu đề và tệp triển khai duy nhất.

Khi tôi viết các lớp, tôi thường giới hạn bản thân mình với một lớp hạng nặng trên mỗi tệp tiêu đề và tệp triển khai. Tôi có thể thêm vào một số chức năng tiện lợi hoặc các lớp trợ giúp nhỏ.

Nếu tôi thấy rằng tệp triển khai dài hàng nghìn dòng, đó thường là dấu hiệu cho thấy có quá nhiều dòng và tôi cần phải chia nhỏ nó.

7

Một chức năng cho mỗi tệp có thể bị lộn xộn theo ý kiến ​​của tôi. Hãy tưởng tượng nếu tiêu đề POSIX và ANSI C được thực hiện theo cùng một cách.

#include <strlen.h> 
#include <strcpy.h> 
#include <strncpy.h> 
#include <strchr.h> 
#include <strstr.h> 
#include <malloc.h> 
#include <calloc.h> 
#include <free.h> 
#include <printf.h> 
#include <fprintf.h> 
#include <vpritnf.h> 
#include <snprintf.h> 

Một lớp cho mỗi tệp là một ý tưởng hay.

+2

Nghe khá tuyệt với tôi, không bao giờ phải tìm kiếm chức năng đầu trang nào ... nhưng họ cũng có thể có các tiêu đề tiện lợi bao gồm tất cả các hàm chuỗi trong chuỗi.h và vv. Mặt khác, các trình biên dịch có thể đưa ra các giả định về việc bao gồm "strlen.h" nếu strlen được sử dụng (với cảnh báo dĩ nhiên) – technosaurus

1

Bạn có thể redeclare một số chức năng của bạn như là phương pháp tĩnh của một hoặc nhiều lớp: điều này mang đến cho bạn một cơ hội (và một lý do tốt) để nhóm một số trong số họ vào một tệp nguồn duy nhất.

Một lý do chính đáng để có hoặc không có nhiều chức năng trong một tệp nguồn, nếu tệp nguồn đó là một đối một với tệp đối tượng và liên kết liên kết toàn bộ tệp đối tượng: nếu tệp thực thi có thể muốn một hàm nhưng không khác, sau đó đặt chúng trong các tệp nguồn riêng biệt (để liên kết có thể liên kết một tệp không có tệp khác).

+0

Tốt hơn các thành viên tĩnh của một lớp đang đặt các hàm trong một không gian tên. Cú pháp để sử dụng các hàm giống nhau, nhưng nó làm cho ý định rõ ràng hơn. – KeithB

+0

Hầu hết các trình liên kết sẽ chỉ lấy các hàm đã được gọi trong tệp thi hành cuối cùng, vì vậy tôi không nghĩ tối ưu hóa cho trình liên kết có ý nghĩa nhiều. – KeithB

1

Tôi cũng đã cố gắng chia tệp trong một hàm cho mỗi tệp nhưng có một số hạn chế. Đôi khi các hàm có xu hướng lớn hơn chúng cần (bạn không muốn thêm một tệp c mới mọi lúc) trừ khi bạn đang siêng năng về việc tái cấu trúc mã của bạn (tôi không phải). Hiện tại tôi đặt 1 đến 3 hàm trong c tệp và nhóm tất cả các tệp c cho một chức năng trong một thư mục. Đối với headerfiles, tôi có Funcionality.hSubfunctionality.h để tôi có thể bao gồm tất cả các chức năng cùng một lúc khi cần hoặc chỉ là một chức năng tiện ích nhỏ nếu toàn bộ gói không cần thiết.

1

Một giáo sư lập trình cũ của tôi đề xuất chia nhỏ các mô-đun cho hàng trăm dòng mã để bảo trì. Tôi không phát triển trong C + + nữa, nhưng trong C# tôi hạn chế bản thân mình với một lớp cho mỗi tập tin, và kích thước của tập tin không quan trọng miễn là không có gì không liên quan đến đối tượng của tôi. Bạn có thể sử dụng các vùng #pragma để giảm bớt không gian trình soạn thảo một cách duyên dáng, không chắc chắn nếu trình biên dịch C++ có chúng, nhưng nếu nó thực hiện thì chắc chắn sử dụng chúng.

Nếu tôi vẫn đang lập trình trong C++, tôi sẽ nhóm các chức năng theo cách sử dụng bằng cách sử dụng nhiều hàm cho mỗi tệp. Vì vậy, tôi có thể có một tập tin gọi là 'Service.cpp' với một vài chức năng xác định rằng "dịch vụ". Có một chức năng cho mỗi tập tin sẽ lần lượt gây ra sự hối tiếc để tìm đường trở lại vào dự án của bạn bằng cách nào đó, một cách nào đó.

Có vài nghìn dòng mã trên mỗi tệp là không cần thiết. Bản thân các hàm sẽ không bao giờ nhiều hơn vài trăm dòng mã. Luôn nhớ rằng một hàm chỉ nên thực hiện một điều và được giữ ở mức tối thiểu. Nếu một hàm hoạt động nhiều hơn một thứ, nó sẽ được tái cấu trúc thành các phương thức trợ giúp.

Không bao giờ đau khi có nhiều tệp nguồn xác định một thực thể đơn lẻ. Tức là: 'ServiceConnection.cpp' 'ServiceSettings.cpp', v.v.

Thỉnh thoảng nếu tôi tạo một đối tượng, và nó sở hữu các đối tượng khác, tôi sẽ kết hợp nhiều lớp vào một tệp duy nhất. Ví dụ một nút điều khiển có chứa các đối tượng 'ButtonLink', tôi có thể kết hợp nó vào trong lớp Button. Đôi khi tôi không, nhưng đó là quyết định "ưu tiên thời điểm".

Làm những gì phù hợp nhất với bạn. Thử nghiệm một chút với các phong cách khác nhau trên các dự án nhỏ hơn có thể hữu ích. Hy vọng điều này sẽ giúp bạn ra một chút.

1

Đối với phần HEADER, bạn nên kết hợp các mục thành các nhóm lôgic và tạo các tệp HEADER của bạn dựa trên đó. Điều này có vẻ và rất hợp lý IMHO.

Đối với phần SOURCE, bạn nên đặt từng triển khai hàm trong một tệp SOURCE riêng biệt. (chức năng tĩnh là ngoại lệ trong trường hợp này) Điều này có vẻ không hợp lý lúc đầu nhưng hãy nhớ, một trình biên dịch biết về các hàm nhưng một trình liên kết chỉ biết về các tệp o/obj và các ký hiệu được xuất của nó.Điều này có thể thay đổi kích thước của tập tin đầu ra đáng kể và đây là một vấn đề rất quan trọng đối với các hệ thống nhúng.

Thanh toán glibc hoặc Visual C++ cây nguồn CRT ...

+0

Ha, tôi nghĩ về nó ngược lại. Tôi không thấy lý do để đặt nhiều hàm vào một tệp HEADER vì sau đó bạn sẽ nhận được các phụ thuộc biên dịch. Nhưng việc đặt nhiều triển khai chức năng vào SOURCE sẽ có ý nghĩa với tôi ... – Frank

+0

Bạn không cần phải dùng từ ngữ của tôi cho nó, chỉ cần kiểm tra mã nguồn C++ trực quan C++ hoặc glibc ... – Malkocoglu

1

tôi có thể thấy một số lợi thế để tiếp cận của bạn, nhưng có một số nhược điểm.

1) Bao gồm gói là cơn ác mộng. Bạn có thể kết thúc với 10-20 bao gồm để có được các chức năng bạn cần. Ví dụ hình ảnh nếu STDIO hoặc StdLib đã được thực hiện theo cách này.

2) Duyệt mã sẽ hơi đau vì nói chung, việc di chuyển qua tệp dễ dàng hơn để chuyển tệp. Rõ ràng là quá lớn của tập tin là khó, nhưng ngay cả với IDE hiện đại nó là khá dễ dàng để thu gọn các tập tin xuống đến những gì bạn cần và rất nhiều trong số họ có chức năng danh sách cắt ngắn.

3) bảo trì tệp là một cơn đau.

4) Tôi là một fan hâm mộ lớn của các chức năng nhỏ và tái cấu trúc. Khi bạn thêm phí (tạo một tệp mới, thêm nó vào điều khiển nguồn, ...) nó khuyến khích mọi người viết các hàm dài hơn thay vì bẻ 1 hàm thành 3 phần, bạn chỉ cần tạo 1 phần lớn.

+0

Re: # 4: Có thể bạn có thể giới hạn bản thân với một hàm "công khai" cho mỗi tệp và cho phép bạn có nhiều hàm tĩnh trong tệp đó khi bạn cần. Bằng cách đó, nếu tôi đang sử dụng mã của bạn và muốn tìm kiếm một hàm, tôi biết chính xác nơi cần tìm. Ngoài ra, bạn giữ khái niệm "mọi thứ trong tệp này có liên quan/cho hàm đơn này." – Ethan

2

Chúng tôi sử dụng nguyên tắc của một hàm bên ngoài cho mỗi tệp. Tuy nhiên, trong tệp này có thể có một vài hàm "trợ giúp" khác trong các không gian tên chưa được đặt tên được sử dụng để thực hiện hàm đó.

Theo kinh nghiệm của chúng tôi, trái với một số nhận xét khác, điều này đã có hai lợi ích chính. Đầu tiên là thời gian xây dựng nhanh hơn vì các mô-đun chỉ cần được xây dựng lại khi các API cụ thể của chúng được sửa đổi. Ưu điểm thứ hai là bằng cách sử dụng một sơ đồ đặt tên thông thường, nó không bao giờ là cần thiết để dành nhiều thời gian tìm kiếm các tiêu đề có chứa các chức năng bạn muốn gọi:

// getShapeColor.h 
Color getShapeColor (Shape); 

// getTextColor.h 
Color getTextColor (Text); 

Tôi không đồng ý rằng các thư viện chuẩn là một ví dụ tốt cho không sử dụng một hàm (bên ngoài) cho mỗi tệp. Các thư viện chuẩn không bao giờ thay đổi và có các giao diện được xác định rõ ràng và do đó không có điểm nào ở trên áp dụng cho chúng.

Điều đó đang được nói, ngay cả trong trường hợp thư viện chuẩn cũng có một số lợi ích tiềm năng trong việc tách các chức năng riêng lẻ. Đầu tiên là các trình biên dịch có thể tạo ra một cảnh báo hữu ích khi sử dụng các phiên bản không an toàn các chức năng, ví dụ: strcpy vs strncpy, theo cách tương tự như cách g ++ được sử dụng để cảnh báo để bao gồm so với.

Một lợi thế khác là tôi sẽ không còn bị phát hiện bằng cách bao gồm bộ nhớ khi tôi muốn sử dụng memmove!

0

Một chức năng cho mỗi tệp có lợi thế kỹ thuật nếu bạn đang tạo thư viện tĩnh (tôi đoán đó là một trong những lý do khiến các dự án như dự án Musl-libc tuân theo mẫu này).

thư viện tĩnh được liên kết với đối tượng tập tin granularity và vì vậy nếu bạn có một thư viện tĩnh libfoobar.a gồm *:

foo.o 
    foo1 
    foo2 
bar.o 
    bar 

sau đó nếu bạn liên kết các lib cho bar chức năng, các thành viên lưu trữ bar.o sẽ nhận được được liên kết nhưng không phải là thành viên foo.o.Nếu bạn liên kết cho foo1, thì thành viên foo.o sẽ được liên kết, mang chức năng foo2 có thể không cần thiết.

Có thể có các cách khác để ngăn các hàm không cần thiết được liên kết trong (-ffunction-sections -fdata-sections--gc-sections) nhưng một hàm cho mỗi tệp có thể đáng tin cậy nhất.


  • Tôi phớt lờ C++ tên mangling đây vì lợi ích của sự đơn giản