Больше техническая заметка, чем статья, поэтому постараюсь изложить мысли как можно кратче.
До того, как начать, если есть желание перейти непосредственно к коду - недавно опубликовал библиотеку.
Приходя из JS/TS мира, когда я впервые написал на Dart, самой прекрасной вещью, помимо многих было использование функций isEmpty или isNotEmpty для String, List, Map, и так далее. Это было невероятно просто и прекрасно не писать каждый раз .length == 0.
Также, очень полезным паттерном были empty/zero значения как Duration.zero, Offset.zero, и другие.
Спустя время, я нашел привычку использовать похожий принцип для работы с различными случаями, а также пришел к мысли - что если мы используем такие значения для большей части объектов, избавляясь от null (не для всех случаев, но тем не менее)? Немного поискав, нашел похожий паттерн в Go и других языках, и продолжил думать:
Проблема
представим ситуацию:
String? value;
// do something
value = "new value”;
Какие проверки мы должны сделать, чтобы быть уверенными что значение действительно существует и мы могли увидеть случай когда нужно применить значение, например по умолчанию? 🤔
final String newValue;
if(value != null && value.isEmpty){
//
newValue = “defaultValue”;
} else {
newValue = value;
}
Какова разница проверки null и isEmpty в такой ситуации? 🤔
Что если мы напишем так:
String value = “”;
final String newValue;
if(value.isEmpty){
// do something
newValue = “defaultValue”;
} else {
newValue = value;
}
// use newValue
Лучше, но всё ещё verbose.
Возможно было бы здорово использовать inline ? Попробуем добавить extension:
extension StringX on String {
String whenEmptyUse(String value) => isEmpty ? value : this;
}
final newValue = value.whenEmptyUse("defaultValue");
Что если мы добавим такой паттерн к lists, maps, values, и numbers?
extension ListX<T> on List<T> {
List<T> whenEmptyUse(List<T> values) => isEmpty ? values : this;
}
final newValues = values.whenEmptyUse(['defaultValue']);
extension DoubleX on double {
bool get isZero => this == 0;
double whenZeroUse(final double value) =>; isZero ? value : this;
}
final opacity = 0;
final newOpacity = opacity.whenZeroUse(0.5);
Такой паттерн также может оказаться очень полезен с zero/empty значениями для объектов. Например:
class Person {
const Person({this.name=''});
final String name;
static const empty = Person();
bool get isEmpty => name.isEmpty;
bool get isNotEmpty => name.isNotEmpty;
// или можно использовать другие названия чтобы сохранить читабельность
bool get isInitialized => name.isNotEmpty;
bool get isNotInitialized => name.isEmpty;
}
extension PersonX on Person {
Person whenEmptyUse(Person value) => isEmpty ? value : this;
}
И так мы можем использовать его везде - проверяя такой объект так же просто как String:
var person = Person.empty;
// do something dangerous
final newPerson = person.whenEmptyUse(Person(name:'Frodo'));
Такой подход может быть полезен и для IDs, Lists с типами, и т.д.
Таким образом получается более простой API, мы всегда можем быть уверены в том, что мы знаем значение, и можем его валидировать значения по умолчанию (empty / zero).
Надеюсь, что этот концепт окажется полезным :-)
Пожалуйста делитесь своими мыслями в комментариях:-) это поможет сделать эту статью видимой для других и будет здоровской поддержкой и мотивацией:-)
Спасибо за ваше время и хорошего дня!
Антон