Sử dụng kiểm tra loại tĩnh để bảo vệ chống lại lỗi kinh doanh


13

Tôi là một fan hâm mộ của kiểm tra loại tĩnh. Nó ngăn bạn khỏi những sai lầm ngu ngốc như thế này:

// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too

Nhưng nó không ngăn bạn mắc những sai lầm ngu ngốc như thế này:

Adult a = new Adult();
// obviously you've mixed up these fields, but type checker won't complain
a.setAge(150); // nobody's ever lived this old
a.setWeight(42); // a 42lb adult would have serious health issues

Vấn đề xảy ra khi bạn đang sử dụng cùng loại để đại diện cho các loại thông tin rõ ràng khác nhau. Tôi đã nghĩ rằng một giải pháp tốt cho việc này sẽ mở rộng Integerlớp, chỉ để ngăn ngừa lỗi logic nghiệp vụ, nhưng không thêm chức năng. Ví dụ:

class Age extends Integer{};
class Pounds extends Integer{};

class Adult{
    ...
    public void setAge(Age age){..}
    public void setWeight(Pounds pounds){...}
}

Adult a = new Adult();
a.setAge(new Age(42));
a.setWeight(new Pounds(150));

Đây có được coi là thực hành tốt? Hoặc có những vấn đề kỹ thuật không lường trước được với thiết kế hạn chế như vậy?


5
a.SetAge( new Age(150) )Vẫn không biên dịch?
John Wu

1
Kiểu int của Java có một phạm vi cố định. Rõ ràng bạn muốn có số nguyên với phạm vi tùy chỉnh, ví dụ: Số nguyên <18, 110>. Bạn đang tìm kiếm các loại sàng lọc hoặc các loại phụ thuộc, mà một số ngôn ngữ (không chính thống) cung cấp.
Theodoros Chatzigiannakis

3
Những gì bạn đang nói là cách tuyệt vời để làm thiết kế! Đáng buồn thay, Java có hệ thống kiểu nguyên thủy đến mức khó có thể làm đúng. Và ngay cả khi bạn cố gắng làm điều đó, nó có thể dẫn đến các tác dụng phụ như hiệu suất thấp hơn hoặc năng suất của nhà phát triển thấp hơn.
Euphoric

@JohnWu có ví dụ của bạn vẫn sẽ biên dịch, nhưng đó là điểm thất bại duy nhất cho logic đó. Khi bạn khai báo new Age(...)đối tượng của mình, bạn không thể gán nó cho một biến kiểu Weightở bất kỳ nơi nào khác. Nó làm giảm số lượng những nơi mà sai lầm có thể xảy ra.
J-bob

Câu trả lời:


12

Về cơ bản, bạn đang yêu cầu một hệ thống đơn vị (không, không phải kiểm tra đơn vị, "đơn vị" như trong "đơn vị vật lý", như mét, vôn, v.v.).

Trong mã của bạn Ageđại diện cho thời gian và Poundsđại diện cho khối lượng. Điều này dẫn đến những thứ như chuyển đổi đơn vị, đơn vị cơ sở, độ chính xác, v.v.


Đã có / đang cố gắng đưa một thứ như vậy vào Java, ví dụ:

Hai người sau dường như đang sống trong điều github này: https://github.com/unitsofmeasairs


C ++ có đơn vị thông qua Boost


LabView đi kèm với một loạt các đơn vị .


Có những ví dụ khác trong các ngôn ngữ khác. (chỉnh sửa được chào đón)


Đây có được coi là thực hành tốt?

Như bạn có thể thấy ở trên, ngôn ngữ càng được sử dụng để xử lý các giá trị với các đơn vị, thì nó càng hỗ trợ các đơn vị. LabView thường được sử dụng để tương tác với các thiết bị đo lường. Vì vậy, nó có ý nghĩa để có một tính năng như vậy trong ngôn ngữ và sử dụng nó chắc chắn sẽ được coi là một thực hành tốt.

Nhưng trong bất kỳ mục đích chung nào, ngôn ngữ cấp cao, nơi nhu cầu thấp đối với mức độ nghiêm ngặt như vậy, điều đó có thể là bất ngờ.

Hoặc có những vấn đề kỹ thuật không lường trước được với thiết kế hạn chế như vậy?

Tôi đoán sẽ là: hiệu suất / bộ nhớ. Nếu bạn xử lý nhiều giá trị, chi phí chung của một đối tượng trên mỗi giá trị có thể trở thành vấn đề. Nhưng như mọi khi: Tối ưu hóa sớm là gốc rễ của mọi tội lỗi .

Tôi nghĩ rằng "vấn đề" lớn hơn đang khiến mọi người quen với nó, vì đơn vị thường được định nghĩa ngầm như vậy:

class Adult
{
    ...
    public void setAge(int ageInYears){..}

Mọi người sẽ bối rối khi họ phải vượt qua một đối tượng như một giá trị cho một thứ gì đó dường như có thể được giải thích đơn giản int, khi họ không quen thuộc với các hệ thống đơn vị.


"Mọi người sẽ bối rối khi họ phải vượt qua một đối tượng như một giá trị cho một thứ gì đó dường như có thể được giải thích bằng một cách đơn giản int..." --- mà chúng tôi có Hiệu trưởng Ít ngạc nhiên nhất như hướng dẫn ở đây. Nắm bắt tốt.
Greg Burghardt

1
Tôi nghĩ rằng phạm vi tùy chỉnh di chuyển điều này ra khỏi vương quốc của một thư viện đơn vị và vào các loại phụ thuộc
jk.

6

Ngược lại với câu trả lời của null, việc xác định loại cho "đơn vị" có thể có lợi nếu một số nguyên không đủ để mô tả phép đo. Chẳng hạn, trọng lượng thường được đo bằng nhiều đơn vị trong cùng một hệ thống đo lường. Hãy nghĩ "pound" và "ounces" hoặc "kilogam" và "gram".

Nếu bạn cần một mức độ chi tiết hơn, việc xác định loại cho đơn vị là có lợi:

public struct Weight {
    private int pounds;
    private int ounces;

    public Weight(int pounds, int ounces) {
        // Value range checks go here
        // Throw exception if ounces is greater than 16?
    }

    // Getters go here
}

Đối với một cái gì đó như "tuổi", tôi khuyên bạn nên tính toán vào thời gian chạy dựa trên ngày sinh của người đó:

public class Adult {
    private Date birthDate;

    public Interval getCurrentAge() {
        return calculateAge(Date.now());
    }

    public Interval calculateAge(Date date) {
        // Return interval between birthDate and date
    }
}

2

Những gì bạn dường như đang tìm kiếm được gọi là các loại được gắn thẻ . Chúng là một cách để nói "đây là một số nguyên biểu thị tuổi" trong khi "đây cũng là một số nguyên nhưng nó đại diện cho trọng số" và "bạn không thể gán cái này cho cái kia". Lưu ý rằng điều này đi xa hơn các đơn vị vật lý như mét hoặc kilôgam: Tôi có thể có trong chương trình "chiều cao của mọi người" và "khoảng cách giữa các điểm trên bản đồ", cả hai đều được đo bằng mét, nhưng không tương thích với nhau kể từ khi gán cho cái khác không có ý nghĩa từ quan điểm logic kinh doanh.

Một số ngôn ngữ, như Scala hỗ trợ các loại được gắn thẻ khá dễ dàng (xem liên kết ở trên). Trong những người khác, bạn có thể tạo các lớp bao bọc của riêng bạn, nhưng điều này ít thuận tiện hơn.

Xác nhận, ví dụ kiểm tra xem chiều cao của một người là "hợp lý" là một vấn đề khác. Bạn có thể đặt mã như vậy trong Adultlớp của bạn (hàm tạo hoặc setters) hoặc bên trong các lớp loại / trình bao bọc được gắn thẻ của bạn. Theo một cách nào đó, các lớp dựng sẵn như URLhoặcUUID hoàn thành vai trò đó (trong số các lớp khác, ví dụ: cung cấp các phương thức tiện ích).

Việc sử dụng các loại được gắn thẻ hoặc các lớp trình bao bọc thực sự sẽ giúp làm cho mã của bạn tốt hơn, sẽ phụ thuộc vào một số yếu tố. Nếu các đối tượng của bạn đơn giản và có ít trường, rủi ro gán sai chúng là thấp và mã bổ sung cần thiết để sử dụng các loại được gắn thẻ có thể không đáng để bỏ công sức. Trong các hệ thống phức tạp với cấu trúc phức tạp và nhiều trường (đặc biệt là nếu nhiều trong số chúng có chung kiểu nguyên thủy), nó thực sự có thể hữu ích.

Trong mã tôi viết, tôi thường tạo các lớp bao bọc nếu tôi đi qua các bản đồ. Các loại như Map<String, String>rất mờ đục, vì vậy gói chúng trong các lớp với các tên có ý nghĩa như NameToAddressgiúp đỡ rất nhiều. Tất nhiên, với các loại được gắn thẻ, bạn có thể viết Map<Name, Address>và không cần trình bao bọc cho toàn bản đồ.

Tuy nhiên, đối với các loại đơn giản như Chuỗi hoặc Số nguyên, tôi đã thấy các lớp trình bao bọc (trong Java) có quá nhiều phiền toái. Logic nghiệp vụ thông thường không tệ lắm, nhưng đã phát sinh một số vấn đề với việc tuần tự hóa các loại này thành JSON, ánh xạ chúng sang các đối tượng DB, v.v. Bạn có thể viết các trình ánh xạ và móc cho tất cả các khung lớn (ví dụ Jackson và Spring Data), nhưng công việc bổ sung và bảo trì liên quan đến mã này sẽ bù đắp bất cứ điều gì bạn có được từ việc sử dụng các hàm bao này. Tất nhiên, YMMV và trong một hệ thống khác, số dư có thể khác nhau.

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.