Làm cách nào để quản lý một bộ quy tắc và số ma thuật rất lớn trong chương trình của tôi?


21

Tôi hơi mới đối với lập trình (Tôi là kỹ sư cơ khí bằng thương mại) và tôi đang phát triển một chương trình nhỏ trong thời gian ngừng hoạt động để tạo ra một phần (solidworks) dựa trên đầu vào từ nhiều người khác nhau từ nhà máy.

Chỉ dựa trên một vài đầu vào (chính xác là 6), tôi cần thực hiện hàng trăm lệnh gọi API có thể nhận tới hàng tá tham số mỗi tham số; tất cả được tạo ra bởi một bộ quy tắc tôi đã thu thập sau khi phỏng vấn mọi người xử lý phần đó. Phần quy tắc và tham số của mã của tôi là 250 dòng và đang phát triển.

Vì vậy, cách tốt nhất để giữ cho mã của tôi có thể đọc và quản lý được là gì? Làm cách nào để tôi tổng hợp tất cả các số ma thuật của mình, tất cả các quy tắc, thuật toán và các phần thủ tục của mã? Làm cách nào để đối phó với API chi tiết và chi tiết?

Mục tiêu chính của tôi là có thể trao cho ai đó nguồn của tôi và để họ hiểu những gì tôi đang làm mà không cần đầu vào của tôi.


7
Bạn có thể cung cấp một số ví dụ về các cuộc gọi API này không?
Robert Harvey


"Tất cả các vấn đề trong khoa học máy tính có thể được giải quyết bằng một mức độ gián tiếp khác" - David Wheeler
Phil Frost

... ngoại trừ quá nhiều cấp độ gián tiếp :)
Dan Lyons

1
Thật khó để trả lời câu hỏi của bạn mà không thấy mã của bạn. Bạn có thể đăng mã của mình trên codereview.stackexchange.com và nhận lời khuyên từ các lập trình viên khác.
Gilbert Le Blanc

Câu trả lời:


26

Dựa trên những gì bạn mô tả, có lẽ bạn sẽ muốn khám phá thế giới cơ sở dữ liệu tuyệt vời. Nghe có vẻ như nhiều con số ma thuật mà bạn mô tả - đặc biệt nếu chúng phụ thuộc một phần - thực sự là dữ liệu, không phải mã. Bạn sẽ gặp nhiều may mắn hơn và thấy việc mở rộng ứng dụng dễ dàng hơn rất nhiều trong thời gian dài, nếu bạn có thể phân loại cách dữ liệu liên quan đến các bộ phận và xác định cấu trúc cơ sở dữ liệu cho nó.

Hãy ghi nhớ, 'cơ sở dữ liệu' không nhất thiết có nghĩa là MySQL hoặc MS-SQL. Cách bạn lưu trữ dữ liệu sẽ phụ thuộc rất nhiều vào cách sử dụng chương trình, cách bạn viết nó, v.v. Nó có thể có nghĩa là cơ sở dữ liệu kiểu SQL hoặc có thể chỉ đơn giản là một tệp văn bản được định dạng.


7
Đồng ý với việc mã hóa dữ liệu trong cơ sở dữ liệu, mặc dù có vẻ như anh ta có vấn đề lớn hơn.
Robert Harvey

Nếu tôi đang tạo một chương trình tạo ra các phần hoàn toàn khác nhau, ừ, đây sẽ là cách tốt nhất. Tuy nhiên, đây chỉ là một phần với bốn cấu hình hơi khác nhau. Nó sẽ không bao giờ là một điều lớn (trừ khi họ thuê một nhà phát triển để làm một cái gì đó giống như vậy, trong trường hợp đó không thành vấn đề). Mặc dù, tôi đoán nó sẽ là một trải nghiệm học tập tuyệt vời sau khi tôi hoàn thành và muốn tái cấu trúc.
dùng2785724

1
Âm thanh như mã hóa mềm . Cơ sở dữ liệu là cho trạng thái đột biến. Số ma thuật không thể thay đổi, theo định nghĩa.
Phil Frost

1
@PhilFrost: Bạn có thể biến chúng thành bất biến. Chỉ không viết thư cho họ sau khi tạo bảng ban đầu.
Robert Harvey

1
@PhilFrost: Chà, giờ tôi đã thấy API mà anh ấy đang xử lý. Nó là đáng chú ý chỉ cho kích thước tuyệt đối của nó. Anh ta có thể không cần một cơ sở dữ liệu, trừ khi anh ta làm.
Robert Harvey

14

Trừ khi bạn dự đoán sẽ mở rộng phần này thành nhiều phần, tôi sẽ không muốn thêm cơ sở dữ liệu. Có một cơ sở dữ liệu có nghĩa là một đống lớn các thứ để học cho bạn và nhiều thứ khác để cài đặt để làm cho nó hoạt động cho người khác. Thêm một cơ sở dữ liệu nhúng sẽ giữ cho khả năng thực thi cuối cùng, nhưng ai đó với mã nguồn của bạn bây giờ có thêm một điều để làm việc.

Tôi nghĩ rằng một danh sách các hằng số được đặt tên rõ ràng và các hàm thực thi quy tắc sẽ giúp ích rất nhiều. Nếu bạn đặt mọi thứ tên tự nhiên và tập trung vào các kỹ thuật lập trình biết chữ, bạn sẽ có thể tạo ra một chương trình dễ đọc.

Lý tưởng nhất là bạn sẽ kết thúc với mã có nội dung:

LeftBearingHoleDepth = BearingWidth + HoleDepthTolerance;
if (not CheckPartWidth(LeftBearingHoleDepth, {other parameters})
    {whatever you need to adjust}

Tùy thuộc vào cách các hằng số cục bộ mà tôi muốn khai báo chúng trong các hàm mà chúng được sử dụng ở những nơi có thể. Nó khá hữu ích để biến:

SomeAPICall(10,324.5, 1, 0.02, 6857);

vào

const NumberOfOilDrainHoles = 10
const OilDrainHoleSpacing = 324.5
{etc}
SomeAPICall(NumberOfOilDrainHoles, OilDrainHoleSpacing, {etc}

Điều đó cung cấp cho bạn phần lớn mã tự viết tài liệu và cũng khuyến khích bất kỳ ai sửa đổi mã để đặt tên có ý nghĩa tương tự với những gì họ thêm. Bắt đầu từ địa phương cũng giúp dễ dàng xử lý tổng số hằng số bạn sẽ tích lũy. Sẽ hơi khó chịu nếu bạn phải tiếp tục cuộn qua một danh sách dài các hằng số để đảm bảo giá trị là giá trị bạn muốn.

Một mẹo cho tên: đặt từ quan trọng nhất ở bên trái. Nó có thể không đọc khá tốt, nhưng nó làm cho việc tìm kiếm mọi thứ dễ dàng hơn. Hầu hết thời gian bạn đang nhìn vào một chiếc bệ và tự hỏi về bu-lông, không nhìn vào một cái bu-lông và tự hỏi nó làm ở đâu, vì vậy hãy gọi nó là SumpBoltThreadPitch chứ không phải BoltThreadPitchSump. Sau đó sắp xếp danh sách các hằng số. Sau đó, để trích xuất tất cả các nốt luồng, bạn có thể lấy danh sách trong trình soạn thảo văn bản và sử dụng chức năng tìm hoặc sử dụng một công cụ như grep để chỉ trả về các dòng có chứa "ThreadPitch".


1
cũng xem xét việc tạo giao diện Fluent
Ian

Đây là một dòng thực tế từ mã của tôi. Liệu nó có ý nghĩa gì đang xảy ra ở đây (đối số là x1, y1, z1, x2, y2, z2 là gấp đôi), nếu bạn biết tên biến có nghĩa là gì? .CreateLine(m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flange_thickness, m_trunion_support_spacing / 2, -((m_flask_length / 2) + m_sand_ledge_width + m_wall_thickness), -m_flask_height + m_flange_thickness)
dùng2785724

Bạn cũng có thể sử dụng ctags với tích hợp trình soạn thảo để tìm các hằng số.
Phil Frost

3
@ user2785724 Đó là một mớ hỗn độn. Nó đang làm gì vậy Là nó làm cho một rãnh có chiều dài và độ sâu cụ thể? Sau đó, bạn có thể tạo một chức năng được gọi là createGroove(length, depth). Bạn cần thực hiện các chức năng mô tả những gì bạn muốn thực hiện như bạn mô tả chúng cho một kỹ sư cơ khí. Đó là những gì lập trình biết chữ là về.
Phil Frost

Đó là lệnh gọi API để vẽ một dòng trong không gian 3d. Mỗi trong số 6 đối số nằm trên các dòng khác nhau trong chương trình. Toàn bộ API là điên rồ. Tôi không biết phải làm thế nào để gây rối, vì vậy tôi đã làm nó ở đó. Nếu bạn biết lệnh gọi API là gì và các đối số của nó, bạn sẽ thấy các điểm cuối là gì, sử dụng các tham số quen thuộc với bạn và có thể liên kết nó lại với phần đó. Nếu bạn muốn làm quen với SolidWorks, API hoàn toàn là mê cung.
dùng2785724

4

Tôi nghĩ rằng câu hỏi của bạn giảm xuống: làm thế nào để tôi cấu trúc một tính toán? Xin lưu ý rằng bạn muốn quản lý "một bộ quy tắc", đó là mã và "một bộ số ma thuật", đó là dữ liệu. (Bạn có thể xem chúng là "dữ liệu được nhúng trong mã của bạn", tuy nhiên chúng là dữ liệu).

Hơn nữa, làm cho mã của bạn "dễ hiểu với người khác" trên thực tế là mục tiêu chung của tất cả các mô hình lập trình (xem ví dụ " Mô hình triển khai " của Kent Beck hoặc " Mã sạch " của Robert C. Martin cho các tác giả trên phần mềm có cùng mục tiêu như bạn, cho bất kỳ chương trình).

Tất cả các gợi ý trong những cuốn sách này sẽ áp dụng cho câu hỏi của bạn. Hãy để tôi trích xuất một số gợi ý cụ thể cho "số ma thuật" và "bộ quy tắc":

  1. Sử dụng hằng số và liệt kê được đặt tên để thay thế số ma thuật

    Ví dụ về hằng số :

    if (partWidth > 0.625) {
        // doSomeApiCall ...
    }
    return (partWidth - 0.625)
    

    nên được thay thế bằng một hằng số được đặt tên để không có thay đổi nào sau này có thể đưa ra một lỗi đánh máy và phá vỡ mã của bạn, ví dụ bằng cách thay đổi cái đầu tiên 0.625nhưng không phải cái thứ hai.

    const double MAX_PART_WIDTH = 0.625;
    
    if (partWidth > MAX_PART_WIDTH) {
        // doSomeApiCall ...
    }
    return (partWidth - MAX_PART_WIDTH)
    

    Ví dụ về liệt kê :

    Việc liệt kê có thể giúp bạn tập hợp các dữ liệu thuộc về nhau. Nếu bạn đang sử dụng Java, hãy nhớ rằng Enums là các đối tượng; các phần tử của chúng có thể chứa dữ liệu và bạn có thể xác định các phương thức trả về tất cả các phần tử hoặc kiểm tra một số thuộc tính. Ở đây một Enum được sử dụng để xây dựng một Enum khác:

    public enum EnginePart {
        CYLINDER (100, Materials.STEEL),
        FLYWHEEL (120, Materials.STEEL),
        CRANKSHAFT (200, Materials.CARBON);
    
        private final double maxTemperature;
        private final Materials composition;
        private EnginePart(double maxTemperature, Materials composition) {
            this.maxTemperature = maxTemperature;
            this.composition = composition;
        }
    }
    
    public enum Materials {
        STEEL,
        CARBON
    }
    

    Ưu điểm là: bây giờ không ai có thể định nghĩa sai EnginePart không được làm từ thép hoặc carbon và không ai có thể giới thiệu EnginePart có tên là "asdfasdf", như trường hợp đó là một chuỗi sẽ được kiểm tra trên nội dung.

  2. Mẫu chiến lượcmẫu phương thức Factory mô tả cách đóng gói "quy tắc" và chuyển chúng cho một đối tượng khác sử dụng chúng (trong trường hợp mẫu Factory, cách sử dụng đang xây dựng một cái gì đó, trong trường hợp mẫu Chiến lược, sử dụng là bất cứ điều gì bạn muốn).

    Ví dụ về mẫu phương thức Factory :

    Hãy tưởng tượng bạn có hai loại động cơ: một nơi mà mỗi phần được kết nối với các máy nén, và một trong đó mỗi phần có thể được tự do kết nối với bất cứ điều gì các bộ phận khác. Chuyển thể từ Wikipedia

    public class EngineAssemblyLine {
        public EngineAssemblyLine() {
            EnginePart enginePart1 = makeEnginePart();
            EnginePart enginePart2 = makeEnginePart();
            enginePart1.connect(enginePart2);
            this.addEngine(engine1);
            this.addEngine(engine2);
        }
    
        protected Room makeEngine() {
            return new NormalEngine();
        }
    }
    

    Và sau đó trong một lớp học khác:

    public class CompressedEngineAssemblyLine extends EngineAssemblyLine {
        @Override
        protected Room makeRoom() {
            return new CompressedEngine();
        }
    }
    

    Phần thú vị là: bây giờ công cụ xây dựng của Hội đồng quản trị của bạn được tách ra khỏi loại Công cụ mà nó đang xử lý. Có lẽ các addEnginephương thức đang gọi một API từ xa ...

    Ví dụ về mẫu Chiến lược :

    Mẫu Chiến lược mô tả cách giới thiệu một chức năng vào một đối tượng để thay đổi hành vi của nó. Hãy để chúng tôi tưởng tượng bạn đôi khi muốn đánh bóng một phần, đôi khi bạn muốn vẽ nó và theo mặc định bạn muốn xem lại chất lượng của nó. Đây là một ví dụ về Python, được điều chỉnh từ Stack Overflow

    class PartWithStrategy:
    
        def __init__(self, func=None) :
            if func:
                self.execute = func
    
        def execute(self):
            # ... call API of quality review ...
            print "Part will be reviewed"
    
    
    def polish():
        # ... call API of polishing department ...
        print "Part will be polished"
    
    
    def paint():
        # ... call API of painting department ...
        print "Part will be painted"
    
    if __name__ == "__main__" :
        strat0 = PartWithStrategy()
        strat1 = PartWithStrategy(polish)
        strat2 = PartWithStrategy(paint)
    
        strat0.execute()  # output is "Part will be reviewed"
        strat1.execute()  # output is "Part will be polished"
        strat2.execute()  # output is "Part will be painted"
    

    Bạn có thể mở rộng điều này để giữ một danh sách các Hành động bạn muốn thực hiện và sau đó gọi chúng lần lượt từ executephương thức. Có lẽ sự khái quát hóa này có thể được mô tả tốt hơn như một mô hình Builder , nhưng này, chúng ta không muốn trở nên kén chọn, phải không? :)


2

Bạn có thể muốn sử dụng một công cụ quy tắc. Công cụ quy tắc cung cấp cho bạn DSL (Ngôn ngữ cụ thể miền) được thiết kế để mô hình hóa các tiêu chí cần thiết cho một kết quả nhất định theo cách dễ hiểu, như được giải thích trong câu hỏi này .

Tùy thuộc vào việc thực hiện công cụ quy tắc, các quy tắc thậm chí có thể được thay đổi mà không cần biên dịch lại mã. Và bởi vì các quy tắc được viết bằng ngôn ngữ đơn giản của riêng họ, chúng cũng có thể được thay đổi bởi người dùng.

Nếu bạn may mắn, có một công cụ quy tắc sẵn sàng sử dụng cho ngôn ngữ lập trình bạn đang sử dụng.

Nhược điểm là bạn phải làm quen với một công cụ quy tắc có thể khó nếu bạn là người mới bắt đầu lập trình.


1

Giải pháp của tôi cho vấn đề này khá khác biệt: các lớp, cài đặt và LOP.

Đầu tiên bọc API trong một lớp. Tìm chuỗi các lệnh gọi API được sử dụng cùng nhau và kết hợp chúng thành các lệnh gọi API của riêng bạn. Cuối cùng, sẽ không có cuộc gọi trực tiếp đến API cơ bản, chỉ gọi các trình bao bọc của bạn. Các cuộc gọi trình bao bọc sẽ bắt đầu trông giống như một ngôn ngữ nhỏ.

Thứ hai, thực hiện một 'trình quản lý cài đặt'. Đây là một cách để liên kết tên với các giá trị động. Một cái gì đó như thế này. Một ngôn ngữ nhỏ khác.

Baseplate.name="Base plate"
Baseplate.length=1032.5
Baseplate.width=587.3

Cuối cùng, thực hiện ngôn ngữ nhỏ của riêng bạn để thể hiện thiết kế (đây là Lập trình hướng ngôn ngữ). Ngôn ngữ này nên dễ hiểu đối với các kỹ sư và nhà thiết kế đóng góp các quy tắc và cài đặt. Ví dụ đầu tiên về một sản phẩm như vậy xuất hiện trong tâm trí là Gnuplot, nhưng có nhiều sản phẩm khác. Bạn có thể sử dụng Python, mặc dù cá nhân tôi sẽ không.

Tôi hiểu rằng đây là một cách tiếp cận phức tạp và có thể quá mức cần thiết cho vấn đề của bạn hoặc yêu cầu các kỹ năng bạn chưa có được. Đó chỉ là cách tôi sẽ làm điều đó.


0

Tôi không chắc là tôi đã trả lời đúng câu hỏi, nhưng có vẻ như bạn nên nhóm các thứ trong một số cấu trúc. Nói nếu bạn đang sử dụng C ++, bạn có thể định nghĩa những thứ như:

struct SomeParametersClass
{
    int   p1;  // this is for that
    float p2;  // this is a different parameter
    ...
    SomeParametersClass() // constructor, assigns default values
    {
        p1 = 42; // the best value that some guy told me
        p2 = 3.14; // looks like a know value, but isn't
    {
};

struct SomeOtherParametersClass
{
    int   v1;  // this is for ...
    float v2;  // this is for ...
    ...
    SomeOtherParametersClass() // constructor, assigns default values
    {
        v1 = 24; // the best value 
        v2 = 1.23; // also the best value
    }
};

Bạn có thể kích hoạt những thứ này khi bắt đầu chương trình:

int main()
{
    SomeParametersClass params1;
    SomeOtherParametersClass params2;
    ...

Sau đó, các lệnh gọi API của bạn sẽ trông như thế (giả sử bạn không thể thay đổi chữ ký):

 SomeAPICall( params1.p1, params1.p2 );

Nếu bạn có thể thay đổi chữ ký của API, thì bạn có thể chuyển toàn bộ cấu trúc:

 SomeAPICall( params1 );

Bạn cũng có thể nhóm tất cả các tham số thành một trình bao bọc lớn hơn:

struct AllTheParameters
{
    SomeParametersClass      SPC;
    SomeOtherParametersClass SOPC;
};

0

Tôi ngạc nhiên khi không có ai khác đề cập đến điều này ...

Bạn đã nói:

Mục tiêu chính của tôi là có thể trao cho ai đó nguồn của tôi và để họ hiểu những gì tôi đang làm mà không cần đầu vào của tôi.

Vì vậy, hãy để tôi nói điều này, hầu hết các câu trả lời khác đang đi đúng hướng. Tôi chắc chắn nghĩ rằng cơ sở dữ liệu có thể giúp bạn ra ngoài. Nhưng một điều khác sẽ giúp bạn hiểu là bình luận, tên biến tốt và tổ chức / phân tách mối quan tâm thích hợp.

Tất cả các câu trả lời khác đều dựa trên kỹ thuật, nhưng họ bỏ qua các nguyên tắc cơ bản mà hầu hết các lập trình viên học hỏi. Vì bạn là một mech engie bằng thương mại, tôi đoán là bạn không quen với phong cách tài liệu này.

Nhận xét và chọn tên biến tốt, cô đọng giúp vô cùng dễ đọc. Cái nào dễ hiểu hơn?

var x = y + z;

Hoặc là:

//Where bandwidth, which was previously defined is (1000 * Info Rate) / FEC Rate / Modulation * carrier spacing / 1000000
float endFrequency = centerFrequency + (1/2 bandwidth);

Đây là ngôn ngữ khá độc lập. Bất kể nền tảng, IDE, ngôn ngữ, v.v. bạn đang làm việc với tài liệu nào, tài liệu phù hợp là cách sạch nhất, dễ nhất để đảm bảo ai đó có thể hiểu mã của bạn.

Tiếp theo là quản lý những con số ma thuật và vô số mối quan tâm, nhưng tôi nghĩ nhận xét của GrandmasterB đã xử lý khá tốt.

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.