Cách sạch nhất để viết phần mềm thủ tục logic bằng ngôn ngữ OO


12

Tôi là một kỹ sư điện và tôi không biết tôi đang làm cái quái gì nữa. Hãy lưu những người duy trì tương lai của mã của tôi.

Gần đây tôi đã làm việc trên một số chương trình nhỏ hơn (bằng C #) có chức năng là "thủ tục" một cách hợp lý. Ví dụ, một trong số đó là chương trình thu thập thông tin từ các cơ sở dữ liệu khác nhau, sử dụng thông tin đó để tạo ra một loại trang tóm tắt, in ra và sau đó thoát ra.

Logic cần thiết cho tất cả điều đó là khoảng 2000 dòng. Tôi chắc chắn không muốn nhét tất cả những thứ đó vào một lần main()và sau đó "dọn sạch" với #regions như một số nhà phát triển trước đây đã làm (rùng mình).

Đây là một số điều tôi đã thử mà không có nhiều sự hài lòng:

Tạo các tiện ích tĩnh cho từng bit chức năng thô, ví dụ như DatabaseInfoGetter, SummaryPageGenerator và PrintUtility. Làm cho chức năng chính trông như:

int main()
{
    var thisThing = DatabaseInfoGetter.GetThis();
    var thatThing = DatabaseInfoGetter.GetThat();
    var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

    PrintUtility.Print(summary);
}

Đối với một chương trình tôi thậm chí đã đi với các giao diện

int main()
{
    /* pardon the psuedocode */

    List<Function> toDoList = new List<Function>();
    toDoList.Add(new DatabaseInfoGetter(serverUrl));
    toDoList.Add(new SummaryPageGenerator());
    toDoList.Add(new PrintUtility());

    foreach (Function f in toDoList)
        f.Do();
}

Không ai trong số này cảm thấy đúng. Khi mã dài hơn, cả hai cách tiếp cận này bắt đầu trở nên xấu.

Cách tốt để cấu trúc các công cụ như thế này là gì?


2
Tôi không chắc chắn rằng đây hoàn toàn là một bản sao, nhưng vui lòng đọc Phương thức đơn có nhiều tham số so với nhiều phương thức phải được gọi theo thứ tự .


@Snowman, với tư cách là người mới viết các đoạn mã lớn hơn, tôi yên tâm khi thấy rằng tôi không phải là người duy nhất gặp phải các loại vấn đề này. Tôi thích câu trả lời của bạn cho câu hỏi đó. Nó giúp.
Lombard

@Lombard ý tưởng giảm độ phức tạp đến mức có thể quản lý là một kỹ năng thực sự tốt để phát triển. Không ai có thể hiểu một cơ sở mã lớn, thậm chí cả Superman (có thể cả Batman). Tìm cách để thể hiện ý tưởng một cách đơn giản và tự ghi lại mã là rất quan trọng.

"Gần đây tôi đã làm việc trên một số chương trình nhỏ hơn (trong C #) có chức năng là" thủ tục "một cách hợp lý. Ví dụ, một trong số đó là chương trình thu thập thông tin từ các cơ sở dữ liệu khác nhau, sử dụng thông tin đó để tạo ra một loại tóm tắt trang, in nó và sau đó thoát. ": Bạn có chắc chắn rằng bạn không nhầm lẫn giữa" thủ tục "với" mệnh lệnh "? Cả hai thủ tục và hướng đối tượng là (có thể) là bắt buộc, tức là chúng có thể diễn tả các hoạt động được thực hiện theo trình tự.
Giorgio

Câu trả lời:


18

Vì bạn không phải là một lập trình viên chuyên nghiệp, tôi khuyên bạn nên gắn bó với sự đơn giản. Lập trình viên sẽ dễ dàng hơn rất nhiều khi lập trình viên lấy mã thủ tục, đã được mô đun hóa của bạn và biến nó thành OO sau đó, hơn là để họ sửa một chương trình OO được viết xấu. Nếu bạn không có kinh nghiệm, có thể tạo các chương trình OO có thể biến thành một mớ hỗn độn không giúp ích gì cho bạn hoặc bất cứ ai đến sau bạn.

Tôi nghĩ rằng bản năng đầu tiên của bạn, mã "thứ này - thứ kia" trong ví dụ đầu tiên là bản nhạc phù hợp. Đó là rõ ràng và rõ ràng những gì bạn muốn làm. Đừng lo lắng quá nhiều về hiệu quả mã, sự rõ ràng quan trọng hơn nhiều.

Nếu một đoạn mã quá dài, hãy chia nó thành các đoạn có kích thước cắn, mỗi đoạn có chức năng riêng. Nếu nó quá ngắn, hãy cân nhắc sử dụng ít mô-đun và sắp xếp nhiều hơn.

---- Bản thảo: Bẫy thiết kế OO

Làm việc thành công với lập trình OO có thể khó khăn. Thậm chí có một số người coi toàn bộ mô hình là thiếu sót. Có một cuốn sách rất hay tôi đã sử dụng khi lần đầu tiên học lập trình OO có tên Thinking in Java (bây giờ là phiên bản thứ 4). Cùng một tác giả có một cuốn sách tương ứng cho C ++. Thực sự có một câu hỏi khác về các lập trình viên đối phó với những cạm bẫy phổ biến trong lập trình hướng đối tượng .

Một số cạm bẫy rất tinh vi, nhưng có rất nhiều cách để tạo ra vấn đề theo những cách rất cơ bản. Ví dụ, một vài năm trước có một nhân viên thực tập tại công ty tôi đã viết phiên bản đầu tiên của một số phần mềm tôi được thừa hưởng và anh ấy đã tạo giao diện cho mọi thứ có thểmột ngày nào đó có nhiều triển khai Tất nhiên, trong 98% các trường hợp chỉ có một triển khai duy nhất, do đó, mã được tải với các giao diện không được sử dụng khiến việc gỡ lỗi rất khó chịu vì bạn không thể lùi lại một cuộc gọi giao diện, vì vậy cuối cùng bạn phải thực hiện tìm kiếm văn bản để triển khai (mặc dù bây giờ tôi sử dụng IntelliJ đã có tính năng "Hiển thị tất cả các triển khai", nhưng vào ngày trước tôi không có điều đó). Quy tắc ở đây cũng giống như đối với lập trình thủ tục: luôn luôn mã hóa một thứ. Chỉ khi bạn có hai hoặc nhiều thứ, hãy tạo ra một sự trừu tượng.

Một loại lỗi thiết kế tương tự có thể được tìm thấy trong Java Swing API. Họ sử dụng mô hình đăng ký xuất bản cho hệ thống menu Xoay. Điều này làm cho việc tạo và gỡ lỗi các menu trong Swing trở thành một cơn ác mộng hoàn toàn. Điều trớ trêu là nó hoàn toàn vô nghĩa. Hầu như không bao giờ có tình huống trong đó nhiều chức năng sẽ cần phải "đăng ký" để nhấp vào menu. Ngoài ra, đăng ký xuất bản là một misfire hoàn chỉnh ở đó bởi vì một hệ thống menu thường luôn được sử dụng. Nó không giống như các chức năng đang đăng ký và sau đó hủy đăng ký ngẫu nhiên. Việc các nhà phát triển "chuyên nghiệp" tại Sun tạo ra một sai lầm như thế này chỉ cho thấy việc các chuyên gia tạo ra những cú vặn vít hoành tráng trong thiết kế OO dễ dàng đến mức nào.

Tôi là một nhà phát triển rất chuyên nghiệp với nhiều thập kỷ kinh nghiệm trong lập trình OO, nhưng thậm chí tôi sẽ là người đầu tiên thừa nhận có rất nhiều điều tôi không biết và thậm chí bây giờ tôi rất thận trọng khi sử dụng nhiều OO. Tôi đã từng nghe những bài giảng dài từ một đồng nghiệp là một người nhiệt tình OO về cách thực hiện các thiết kế cụ thể. Anh ấy thực sự biết những gì anh ấy đang làm, nhưng thành thật mà nói tôi đã có một thời gian khó hiểu các chương trình của anh ấy bởi vì họ có các mô hình thiết kế tinh vi như vậy.


1
+1 cho "sự rõ ràng quan trọng hơn nhiều [so với hiệu quả]"
Stephen

Bạn có thể chỉ cho tôi một liên kết mô tả những gì cấu thành một "chương trình OO được viết xấu" hoặc thêm một số thông tin về điều đó trong một chỉnh sửa không?
Lombard

@Lombard Tôi đã làm điều đó.
Tyler Durden

1

Cách tiếp cận đầu tiên là tốt. Trong các ngôn ngữ thủ tục, như C, cách tiếp cận tiêu chuẩn là phân chia chức năng thành các không gian tên - sử dụng các lớp tĩnh giống như DatabaseInfoGettervề cơ bản là điều tương tự. Rõ ràng cách tiếp cận này dẫn đến mã đơn giản hơn, mô đun hơn và dễ đọc / dễ bảo trì hơn là đẩy mọi thứ vào một lớp, ngay cả khi mọi thứ được chia thành các phương thức.

Nói về điều đó, cố gắng hạn chế các phương pháp để thực hiện càng ít hành động càng tốt. Một số lập trình viên thích độ chi tiết ít hơn một chút , nhưng các phương pháp lớn luôn bị coi là có hại.

Nếu bạn vẫn đang vật lộn với sự phức tạp, rất có thể bạn cần phải chia nhỏ chương trình của mình hơn nữa. Tạo nhiều cấp độ hơn trong hệ thống phân cấp - có thể DatabaseInfoGettercần tham khảo một số lớp khác như ProductTableEntryhoặc một cái gì đó. Bạn đang viết mã thủ tục nhưng hãy nhớ rằng, bạn đang sử dụng C # và OOP cung cấp cho bạn một loạt các công cụ để giảm độ phức tạp, ví dụ:

int main() 
{
    var thisthat = Database.GetThisThat();
}

public class ThisThatClass
{
    public String This;
    public String That;
}

public static class Database 
{
    public ThisThatClass GetThisThat()
    {
        return new ThisThatClass 
        {
            This = GetThis(),
            That = GetThat()
        };
    }

    public static String GetThis() 
    { 
        //stuff
    }
    public static String GetThat() 
    { 
        //stuff
    }

}

Ngoài ra, hãy cẩn thận với các lớp tĩnh. Các lớp cơ sở dữ liệu là ứng cử viên tốt .. miễn là bạn thường có một cơ sở dữ liệu .. bạn có ý tưởng.

Cuối cùng, nếu bạn nghĩ trong các hàm toán học và C # không phù hợp với phong cách của bạn, hãy thử một cái gì đó như Scala, Haskell, v.v ... Chúng cũng rất tuyệt.


0

Tôi đang trả lời trễ 4 năm nhưng khi bạn đang cố gắng thực hiện mọi thứ theo thủ tục bằng ngôn ngữ OO thì bạn đang làm việc trên một antipotype. Đó không phải là điều bạn nên làm! Tôi tìm thấy một blog giải quyết vấn đề này và cho thấy một giải pháp cho nó, nhưng nó đòi hỏi rất nhiều tái cấu trúc và thay đổi tư duy. Xem mã thủ tục trong mã hướng đối tượng để biết thêm chi tiết.
Như bạn đã đề cập "cái này" và "cái kia", về cơ bản, bạn đang gợi ý rằng bạn có hai lớp khác nhau. Một lớp này (tên xấu!) Và một lớp đó. Và bạn có thể dịch ví dụ này:

var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

vào đây:

var summary = SummaryPageGenerator.GeneratePage(new This(DatabaseInfoGetter), new That(DatabaseInfoGetter));

Bây giờ, thật thú vị khi thấy hơn 2.000 dòng mã thủ tục đó và xác định cách các phần khác nhau có thể được nhóm lại trong các lớp riêng biệt và chuyển các thủ tục khác nhau xuống các lớp đó.
Mỗi lớp nên đơn giản và tập trung vào việc chỉ làm một việc. Và dường như đối với tôi, một số phần của mã đã xử lý các siêu lớp, điều đó thực sự quá lớn để trở nên hữu ích. Ví dụ, DatabaseInfoGetter nghe có vẻ khó hiểu. Dù sao nó cũng nhận được? Nghe có vẻ quá chung chung. Tuy nhiên, SummaryPageGenerator rất cụ thể! Và PrintUtility đang được chung chung một lần nữa.
Vì vậy, để bắt đầu, bạn nên tránh các tên chung cho các lớp của mình và bắt đầu sử dụng các tên cụ thể hơn để thay thế. Ví dụ, lớp PrintUtility sẽ là một lớp In với lớp Header, lớp Footer, lớp TextBlock, lớp Image và có thể là một cách nào đó để chỉ ra cách chúng sẽ chảy trong bản in. Tất cả điều này có thể trong Không gian tên PrintUtility , mặc dù.
Đối với cơ sở dữ liệu, câu chuyện tương tự. Ngay cả khi bạn sử dụng Entity Framework để truy cập cơ sở dữ liệu, bạn nên giới hạn nó trong những thứ bạn sử dụng và chia nó thành các nhóm logic.
Nhưng tôi tự hỏi nếu bạn vẫn giải quyết vấn đề này sau 4 năm. Nếu vậy, thì đó là một suy nghĩ cần phải được thay đổi khỏi "khái niệm thủ tục" và thành "khái niệm hướng đối tượng". Đó là thử thách nếu bạn không có kinh nghiệm ...


-3

Tôi là ý kiến ​​của tôi, bạn nên thay đổi mã của mình sao cho đơn giản, nhất quán và dễ đọc.

Tôi đã viết một số bài đăng trên blog về chủ đề này và tôi tin rằng chúng rất đơn giản để hiểu: Mã tốt là gì

Đơn giản: Giống như một bảng mạch chứa các phần khác nhau, mỗi phần có một trách nhiệm. Mã nên được chia thành các phần nhỏ hơn, đơn giản hơn.

Nhất quán: Sử dụng patters, một tiêu chuẩn để đặt tên các yếu tố và sự vật. Bạn thích các công cụ hoạt động theo cùng một cách mọi lúc và bạn muốn biết nơi để tìm thấy chúng. Nó giống với mã.

Có thể đọc được: Không sử dụng các từ viết tắt trừ khi chúng được sử dụng rộng rãi và được biết đến trong miền mà phần mềm đang nhắm mục tiêu (mattnz), thích các tên có thể phát âm rõ ràng và dễ hiểu.


1
Từ viết tắt được chấp nhận nếu chúng được sử dụng rộng rãi và được biết đến trong miền mà phần mềm đang nhắm mục tiêu. Hãy thử viết phần mềm Kiểm soát không lưu mà không sử dụng - chúng tôi có các từ viết tắt được tạo từ các từ viết tắt - không ai có thể biết bạn đang nói gì về việc bạn đã sử dụng tên đầy đủ. Những thứ như HTTP cho mạng, ABS trong ô tô, vv là hoàn toàn chấp nhận được.
mattnz
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.