Rắc rối nắm bắt mã sạch trông như thế nào trong cuộc sống thực


10

Tôi hiện đang đọc và làm việc thông qua "Mã sạch: Cẩm nang về thủ công phần mềm linh hoạt" của Robert C. Martin. Tác giả nói về cách một chức năng chỉ nên làm một việc, và do đó tương đối ngắn. Cụ thể Martin viết:

Điều này ngụ ý rằng các khối trong câu lệnh if, câu lệnh khác, trong khi câu lệnh, v.v. nên dài một dòng. Có lẽ dòng đó nên là một chức năng gọi. Điều này không chỉ giữ cho hàm bao quanh nhỏ, mà còn thêm giá trị tài liệu vì hàm được gọi trong khối có thể có một tên mô tả độc đáo.

Điều này cũng ngụ ý rằng các chức năng không nên đủ lớn để giữ các cấu trúc lồng nhau. Do đó, mức thụt lề của hàm không được lớn hơn một hoặc hai. Điều này, tất nhiên, làm cho các chức năng dễ đọc và dễ hiểu hơn

Điều này có ý nghĩa, nhưng dường như mâu thuẫn với các ví dụ về những gì tôi thấy là mã sạch. Lấy phương pháp sau đây làm ví dụ:

    public static boolean millerRabinPrimeTest(final int n) {
        final int nMinus1 = n - 1;
        final int s = Integer.numberOfTrailingZeros(nMinus1);
        final int r = nMinus1 >> s;
        //r must be odd, it is not checked here
        int t = 1;
        if (n >= 2047) {
            t = 2;
        }
        if (n >= 1373653) {
            t = 3;
        }
        if (n >= 25326001) {
            t = 4;
        } // works up to 3.2 billion, int range stops at 2.7 so we are safe :-)
        BigInteger br = BigInteger.valueOf(r);
        BigInteger bn = BigInteger.valueOf(n);

        for (int i = 0; i < t; i++) {
            BigInteger a = BigInteger.valueOf(SmallPrimes.PRIMES[i]);
            BigInteger bPow = a.modPow(br, bn);
            int y = bPow.intValue();
            if ((1 != y) && (y != nMinus1)) {
                int j = 1;
                while ((j <= s - 1) && (nMinus1 != y)) {
                    long square = ((long) y) * y;
                    y = (int) (square % n);
                    if (1 == y) {
                        return false;
                    } // definitely composite
                    j++;
                }
                if (nMinus1 != y) {
                    return false;
                } // definitely composite
            }
        }
        return true; // definitely prime
    }
}

Mã này được lấy từ repo mã nguồn Apache Commons tại: https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/primes/SmallPrimes.java

Phương pháp này rất dễ đọc đối với tôi. Đối với các triển khai thuật toán như thế này (triển khai Kiểm tra tính nguyên thủy của Miller-Rabin), liệu có phù hợp để giữ mã như hiện tại và vẫn coi đó là 'sạch', như được định nghĩa trong sách không? Hoặc thậm chí một cái gì đó đã có thể đọc được vì lợi ích này từ việc trích xuất các phương thức để làm cho thuật toán về cơ bản là một chuỗi các hàm gọi "chỉ làm một việc"? Một ví dụ nhanh về trích xuất phương thức có thể là di chuyển ba câu lệnh if đầu tiên sang một hàm như:

private static int getTValue(int n)
    {
        int t = 1;
        if (n >= 2047) {
            t = 2;
        }
        if (n >= 1373653) {
            t = 3;
        }
        if (n >= 25326001) {
            t = 4;    
        }
        return t;
    }

Lưu ý: Câu hỏi này khác với câu hỏi có thể trùng lặp (mặc dù câu hỏi đó cũng hữu ích với tôi), vì tôi đang cố xác định xem tôi có hiểu ý định của tác giả của Clean Code hay không và tôi đang cung cấp một ví dụ cụ thể để làm cho mọi thứ nhiều hơn bê tông.


3
theo như tôi có thể thấy chức năng này chỉ làm một điều ... nó không có tác dụng phụ mà tôi có thể thấy. Điều gì làm cho bạn nghĩ rằng nó có thể không sạch sẽ? Phần nào của chức năng này bạn sẽ coi là xứng đáng được đưa vào một chức năng khác để làm cho nó sạch hơn?
Newtopian

14
Tiêu đề câu hỏi của bạn yêu cầu tình huống "đời thực", và sau đó ví dụ của bạn trông giống như một ví dụ hoàn hảo về chức năng phi thực tế (ít nhất, đối với 99,9% nhà phát triển ứng dụng hoặc web). Nó có thể là một chức năng thực tế cho các nhà lý thuyết số, nhà toán học hoặc nhà khoa học máy tính làm việc trong lĩnh vực cụ thể đó, tất nhiên.
Doc Brown


2
Vâng, đối với tôi đây là cuộc sống thực khi tôi hiện đang phát triển trong lĩnh vực lý thuyết số đại số tính toán :)
1

2
Tôi có thể sẽ cấu trúc lại getTFactor () như bạn mô tả.
user949300

Câu trả lời:


17

"Mã sạch" không phải là một kết thúc, nó là một phương tiện để kết thúc. Mục đích chính của việc tái cấu trúc các hàm lớn hơn thành các hàm nhỏ hơn và làm sạch mã theo các cách khác là để giữ cho mã có thể phát triển và duy trì được.

Khi chọn một thuật toán toán học rất cụ thể như bài kiểm tra chính "Miller-Rabin" từ sách giáo khoa, hầu hết các lập trình viên không muốn phát triển nó. Mục tiêu tiêu chuẩn của họ là chuyển nó từ mã giả của sách giáo khoa thành ngôn ngữ lập trình của môi trường của họ. Với mục đích này, tôi khuyên bạn nên theo dõi sách giáo khoa càng gần càng tốt, điều này thường có nghĩa là không tái cấu trúc.

Tuy nhiên, đối với ai đó làm nhà toán học trong lĩnh vực đó đang cố gắng làm việc với thuật toán đó và thay đổi hoặc cải thiện nó, IMHO chia hàm này thành các hàm nhỏ hơn, được đặt tên tốt hoặc thay thế bó "số ma thuật" bằng các hằng số có tên, có thể giúp thay đổi mã dễ dàng hơn như đối với bất kỳ loại mã nào khác.


1
Điều này thật đúng với gì mà tôi đã tìm kiếm. Tôi đã gặp khó khăn khi xác định khi nào nên sử dụng các thực hành mã sạch trong lĩnh vực tôi đang phát triển. Câu trả lời của bạn cung cấp sự rõ ràng mà tôi đang tìm kiếm. Cảm ơn!
1
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.