AutotestЯк
745 subscribers
6 photos
33 links
Новини, тексти, ідеї, навчальні матеріали на тему “Як писати автотести”, – на Python, Java, C#, JavaScript/TypeScript;)
Cайт: https://autotest.how/uk
Download Telegram
Пташки тут наспівали, що не всім очевидно навіщо і як використовувати Optional в програмуванні і чому «чисті null це зло»... Давай же розберемось та ще й порівняємо підходи в різних мовах – #java, #typescript, #csharp, #python – аби зрозуміти основоположні принципи та механізми 😉

Почнемо з Java, в якій підхід – якраз самий класичний, простий і надійний.

Припустимо ми робимо якусь операцію - «зберегти файл» і хочемо запам'ятати результат (власне збережений файл), але ж результату може і не бути, бо наприклад полетить помилка:


File storedFile = null;

try {

storedFile = storeFile("~/tmp/temp.txt");

} catch (Exception error) {

// що завгодно, можемо залогувати помилку чи ще що...

}

// далі щось може відбуватись ...

// і в кінці, ми повертаємось до нашого файлу...
// наприклад, щоб запам'ятати його ім'я...
// чи просто надрукувати:

System.out.printLine(storedFile.getName());



Код скомпілюється, все ймовірно запрацює, а потім – одного дня, коли по якійсь причині, наприклад не буде права на запис - код грохнеться з чимось типу NullPointerException і з повідомлення про помилку – ми ох як довго не зрозуміємо у чому справа 🙂

Ну... ми поколупаємось, розберемось і підправимо код:


File storedFile = null;

try {

storedFile = storeFile("~/tmp/temp.txt");

} catch (Exception error) {

// ...

}

// ...

System.out.printLine(storedFile != null ? storedFile.getName() : "file was not stored!");



Так от, давні коДаóси помітили :), що коли ми працюємо з нулами то постійно вилазять такого роду проблеми, і краще щось робити для того, щоб «не забувати обробляти нули одразу».

Саме просте, що іноді (наприклад у версіях джави до 9-тої) – я роблю коли під рукою немає спеціального засобу типу Optional (про який буде мова далі) – це просто міняю назву змінної в якій може бути null (себто відсутність результату, пустота):


File maybeStoredFile = null;

try {

maybeStoredFile = storeFile("~/tmp/temp.txt");

} catch (Exception error) {

// ...

}

// ...

/*

// тепер нижче у мене руки не повернуться написати:

System.out.printLine(maybeStoredFile.getName());

// тому що прямим же текстом написано MAYBE stored file!
// тобто може він і є, а може і нема,
// значить перед тим як на ньому викликати якийсь get* треба спочатку перевірити на нулл:

*/

System.out.printLine(maybeStoredFile != null ? storedFile.getName() : "file was not stored!");



Але все ще, це такий собі лайфхак – все залежить від моєї уважності 🙂

Ідеальний же варіант, це коли сам компілятор мені допомагатиме не забути перевірити на відсутність результату... Класичне вирішення проблеми - це використання типу Optional (в деяких мовах програмування його називають Maybe по аналогії з моїм префіксом вище, але не суть):


Optional<File> storedFile = Optional.empty();

try {

storedFile = Optional.of(storeFile("~/tmp/temp.txt"));

} catch (Exception error) {

// ...

}

// ...

/*

// тепер нижче якщо ми забудемо,
// що вище не обов'язково файл був збережений,
// то сам компілятор не дозволить нам написати:

System.out.printLine(storedFile.getName());

// тепер єдиний спосіб отримати що ми хочемо це витягнути його з "коробки optional"
// а раз будемо витягувати то ясно що треба спочатку перевірити чи в коробкі щось є:

*/

System.out.printLine(storedFile.isPresent() ? storedFile.get().getName() : "file was not stored!");



Зверни увагу чим останній код відрізняються від варіанту з null:


System.out.printLine(maybeStoredFile != null ? storedFile.getName() : "file was not stored!");


Виходить з Optional – та ж сама логіка, тільки більше задроства – суть якого в тому, щоб ми не забули що треба перевірити, і саме щоб нагадував нам про це – сам компілятор ще на стадії коли ми пишемо код;)

Що там в інших мовах? – гортай далі 😉 ⬇️
4🔥21👍1🎉1
Отже, ми встигли розібрати ⬆️ що null-и (в Python – None) можуть бути болючими бо приводять до незрозумілих помилок. В #java та і в принципі у більшості мов програмування існує простий механізм – тип Optional (іноді називають його Maybe, іноді ще й обзивають особливим словом Монада...). Це універсальний, масимально простий і надійний механізм, тому якщо ти не джавіст, все одно проскроль вище і почитай;)

При цьому у інших мовах програмування є більш легкі механізми, і зараз ми поглянемо їх на прикладі #typescript, #csharp та #python, і подумаємо чи кращі вони за Optional у Java.

У мовах типу TypeScript чи C# за допомогою спеціальних опцій компілятора, можна включити режим "строгості нуллів" (`--strictNullChecks` чи "strictNullChecks": true для tsconfig.json в TS; <Nullable>enable</Nullable> в C#), і тоді тої ж самої цілі можна досягти навіть з нулами:


// код на TypeScript
let storedFile: File | null = null;

try {

storedFile = storeFile("~/tmp/temp.txt");

} catch (error) {

// ...

}

// ...

/*

// тепер нижче компілятор не дасть написати:

console.log(storedFile.name);

// ми будемо змушені або свідомо звернутись до name через синтаксис !.

console.log(storedFile!.name);

// або ж спочатку перевірити на null:

*/


console.log(storedFile != null ? storedFile.name : "file was not stored!");



В C# працюватиме все точно так само:


// тільки оголошуємо тип як nullable через синтаксис знаку питання
FileStream? storedFile = null;

try {

storedFile = storeFile("~/tmp/temp.txt");

} catch (Exception error) {

// ...

}

/*

// тепер нижче компілятор не дасть написати:

System.Console.WriteLine(storedFile.Name);

// ми будемо змушені або свідомо звернутись до Name через синтаксис !.

System.Console.WriteLine(storedFile!.Name);

// або ж спочатку перевірити на null:

*/

System.Console.WriteLine(storedFile != null ? storedFile.Name : "file was not stored!");


В Python та ж історія що і в TypeScript чи C#, тільки треба додатково встановлювати mypy та плагін до нього:


stored_file: Optional[TextIO] = None

try:
stored_file = store_file("~/tmp/temp.txt")
except Exception as e:
...

# ...

'''

# тепер якщо встановлений додатковий пакет mypy
# та опційно налаштований плагін для нього в редакторі
# то тайп чекер mypy не дасть написати:

print(stored_file.name)

# ми будемо змушені або свідомо через коментар ігнорувати помилку:

print(stored_file.name) # type: ignore)

# або ж спочатку перевірити на None:

'''

print(stored_file.name if stored_file is not None else "file was not stored!")


Тепер питання - чи пощастило TS, C# та частково Python більше ніж Java з його Optional замість спеціального синтаксису? А ось цієї відповіді почекаємо в наступному пості, стей тюнд 😉

P.S.
Діліться в коментарях доповненнями до прикладів вище (вони доволі чорнові), і є повно цікавих нюансів, які не хотілось поки впихувати в сам пост. Також цікаво хто ще що використовував, і в джава і в інших мовах є куча альтернативних способів ;)
7👍7