Використання статичної перевірки типу для захисту від бізнес-помилок


13

Я великий фанат перевірки статичного типу. Це заважає вам робити такі дурні помилки:

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

Але це не заважає вам робити такі дурні помилки:

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

Проблема виникає, коли ви використовуєте один і той же тип для представлення очевидно різних видів інформації. Я думав, що хорошим рішенням для цього буде розширення Integerкласу, лише для запобігання помилок ділової логіки, але не додавання функціональності. Наприклад:

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));

Це вважається хорошою практикою? Або є непередбачені інженерні проблеми в дорозі з таким обмежувальним дизайном?


5
Ви a.SetAge( new Age(150) )все ще не будете компілювати?
Джон Ву

1
Тип int Java має фіксований діапазон. Зрозуміло, що ви хочете мати цілі числа зі спеціальними діапазонами, наприклад, Integer <18, 110>. Ви шукаєте типи уточнення або залежні типи, які пропонують деякі (не основні) мови.
Теодорос Чатзіґянакікіс

3
Те, про що ви говорите, - це чудовий спосіб зробити дизайн! На жаль, Java має настільки примітивний тип типу, що важко зробити правильно. І навіть якщо ви спробуєте це зробити, це може призвести до таких побічних ефектів, як зниження продуктивності або зниження продуктивності розробника.
Ейфорія

@JohnWu так, ваш приклад все одно буде складатись, але це єдина точка провалу для цієї логіки. Після оголошення вашого new Age(...)об'єкта ви не можете неправильно призначити його змінній типу Weightв будь-якому іншому місці. Це зменшує кількість місць, де можуть трапитися помилки.
J-bob

Відповіді:


12

Ви, по суті, запитуєте одиничну систему (ні, не одиничні тести, "одиниця", як у "фізичній одиниці", наприклад, лічильники, вольт тощо).

У вашому коді Ageпредставлений час і Poundsпредставляє масу. Це призводить до таких речей, як перетворення одиниць, базових одиниць, точності тощо.


Були / є спроби ввести таке в Java, наприклад:

Пізніші два, здається, живуть у цій штуці github: https://github.com/unitsofmeasurement


C ++ має одиниці через Boost


LabView поставляється з купою одиниць .


На інших мовах є й інші приклади. (правки вітаються)


Це вважається хорошою практикою?

Як ви бачите вище, чим більша ймовірність того, що мова використовується для обробки значень з одиницями, тим природніше вона підтримує одиниці. LabView часто використовується для взаємодії з вимірювальними пристроями. Як такий, має сенс мати таку особливість у мові, а використання її, безумовно, вважатиметься хорошою практикою.

Але в будь-якій мові високого рівня загального призначення, де попит на таку жорсткість низький, це, мабуть, несподівано.

Або є непередбачені інженерні проблеми в дорозі з таким обмежувальним дизайном?

Моя здогадка буде: продуктивність / пам'ять. Якщо ви маєте справу з великою кількістю значень, накладні витрати об'єкта на значення можуть стати проблемою. Але як завжди: передчасна оптимізація - корінь усього зла .

Я думаю, що більша "проблема" полягає в тому, щоб люди звикли до неї, оскільки підрозділ зазвичай неявно визначається так:

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

Люди будуть розгублені, коли їм доведеться передавати об'єкт як значення для чогось, що, здавалося б, може бути зневажено простим int, коли вони не знайомі з одиничними системами.


"Люди будуть розгублені, коли їм доведеться передавати об'єкт як цінність для чогось, що, здавалося б, може бути зневажено простим int..." --- для чого у нас є керівництво найменшого сюрпризу. Хороший улов.
Грег Бургхардт

1
Я думаю, що власні діапазони переміщують це із сфери бібліотеки одиниць і переходять у залежні типи
jk.

6

На відміну від нульової відповіді, визначення типу для "одиниці" може бути корисним, якщо цілого числа недостатньо для опису вимірювання. Наприклад, вага часто вимірюється в декількох одиницях в межах однієї вимірювальної системи. Подумайте "фунти" і "унції" або "кілограми" і "грами".

Якщо вам потрібен більш детальний рівень вимірювання, визначення типу для одиниці вигідно:

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
}

Для чогось типу "вік" я рекомендую обчислити, що під час проходження виходячи з дати народження людини:

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

Те, що ви, здається, шукаєте, відомо як типи з тегами . Вони є способом сказати "це ціле число, яке представляє вік", а "це також ціле число, але воно означає вагу" і "ви не можете присвоїти одне інше". Зауважте, що це йде далі, ніж фізичні одиниці, такі як метри або кілограми: я можу мати у своїй програмі "висоту людей" та "відстані між точками на карті", обидва виміряні в метрах, але не сумісні одна з одною з моменту призначення одного інший не має сенсу з точки зору ділової логіки.

Деякі мови, як-от Scala, підтримують теги типів досить легко (див. Посилання вище). В інших можна створити власні класи обгортки, але це менш зручно.

Валідація, наприклад перевірка того, чи зростає людина, "розумний" - це інше питання. Ви можете поставити такий код у своєму Adultкласі (конструкторі чи сеттерах) або всередині класів з тегами / обгортки. Певним чином, вбудовані класи, такі як URLабо UUIDвиконують таку роль (серед інших, наприклад, надання корисних методів).

Чи буде використання типів тегів або класів обгортки насправді покращить ваш код, залежатиме від кількох факторів. Якщо ваші об’єкти прості і мають небагато полів, ризик визначити їх неправильними низький, а додатковий код, необхідний для використання тегів типів, не варто витрачати зусиль. У складних системах зі складними структурами та великою кількістю полів (особливо якщо багато з них мають один і той же примітивний тип), це може бути корисним.

У коді, який я пишу, я часто створюю обгорткові класи, якщо передаю карти. Такі типи, як Map<String, String>самі по собі, є непрозорими, тому їх додавання до класів із значущими назвами, такими як NameToAddressбагато, допомагає. Звичайно, з тегами типів ви можете писати Map<Name, Address>та не потребувати обгортці для всієї карти.

Однак для простих типів, таких як Strings або Integers, я виявив, що класи обгортки (на Java) занадто великі неприємності. Регулярна бізнес-логіка була не такою поганою, але виникли ряд проблем із серіалізацією цих типів у JSON, зіставленням їх на об’єкти БД тощо. Ви можете писати картографи та гачки для всіх великих кадрів (наприклад, Jackson та Spring Data), але додаткові роботи та обслуговування, пов'язані з цим кодом, компенсують все, що ви отримаєте від використання цих обгортків. Звичайно, у YMMV і в іншій системі баланс може бути різним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.