LINUX.ORG.RU

Java, философия использования null-ов и Optional-ов

 , , ,


0

5

Насколько я понял замысел создателей Java - для хранения данных нужно использовать nullable поля. В параметрах публичных функций нужно принимать not-null значения, null нельзя (в приватных можно). Если хочешь принять null, то тебе надо сделать перегрузку для этой функции без соотв. параметра или спроектировать API по-другому. В возвращаемых значениях публичных функций надо использовать Optional (вместо возврата null). В локальных переменных - я точно не понял, но полагаю, что в общем случае nullable, но если из функции данные получил в виде Optional, то можно так и оставить.

В целом это всё вызвано желанием убрать ненужное создание объектов. Optional это полновесный объект, и на ровном месте его создавать не нужно. Также доступ к значению получается через промежуточную ссылку, это тоже не очень хорошо. С локальными переменными и полями можно и без Optional разобраться, оно как бы всё рядом. С параметрами методов - тоже можно разобраться путем создания доп перегрузок. И только с возвращаемыми значениями так разобраться не получится, поэтому тут Optional это необходимое зло.

В Idea есть предупреждения на Optional в параметрах метода и в поле, так что это я как бы не сам придумал. Ну и в статьях про это писали.

Всё это выливается в подобный код:

class Person {
  private final String name;
  private final Integer age;
  
  private Person(String name, Integer age) {
    this.name = requireNonNull(name);
    this.age = age;
  }

  String name() { return name; }

  Optional<Integer> age() { return Optional.ofNullable(age); }

  class Builder {
    private String name;
    private Integer age;

    void name(String name) { this.name = requireNonNull(name); }

    void age(Integer age) { this.age = requireNonNull(age); }

    Person build() { return new Person(name, age); }
  }

}

Тут соблюдены все вышеперечисленные «каноны». Если хочешь создать персону без возраста, ты обязан не вызывать метод age в билдере.

С таким подходом получается, что создать копию персоны можно только так:

var personCopyBuilder = new Person.Builder();
personCopyBuilder.name(person.name());
person.age().ifPresent(age -> personCopyBuilder.age(age));
var personCopy = personCopyBuilder.build();

Если взять язык с nullable системой типов вроде Kotlin, то там как-то всё получается проще и естественней…

В частности мне не нравится вот эта сложная передача null, которая в том числе не даёт chain-ить методы билдера.

Как вы думаете - что тут можно улучшить? Или это хороший код и всё ок?

Скажем, один из вариантов это убрать проверку в Builder.age(Integer) на null, и тогда можно будет писать

var personCopy = new Person.Builder()
    .name(person.name())
    .age(person.age().orElse(null))
    .build()

и по сути тут вроде ничего страшного и нет, кроме нарушения правила не принимать nonnull значения в параметрах.

★★★★

Последнее исправление: vbr (всего исправлений: 4)
Ответ на: комментарий от cocucka_B_TECTE

Я про Java, а не про @Nullable Java. Как в компиляторе появится, так буду думать. А использовать какие-то мутные тулзы с неформализованной семантикой - ты ещё про ломбок вспомни, прости господи.

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)

Тут соблюдены все вышеперечисленные «каноны».

Нет, это какая-то пародия на шаблон билдер в задумке потокобезопасный, но с такой реализацией нет.

private Integer age;

Плюс распаковка пустой оболочки приведёт к nullpointerexception

this.name = requireNonNull(name);

А это даже не скомпилится.

С таким подходом получается, что создать копию персоны можно только так:

Хочется клонов, то почему не реализовать интерфейс Cloneable и соотвествующий метод интерфейса?

Ygor ★★★★★
()

В параметрах публичных функций нужно принимать not-null значения, null нельзя (в приватных можно).

Это правило кажется кривым. Если по логике значение может быть null, то не вижу никаких проблем не стесняться этого в публичном интерфейсе.

Проблемы с не nullable полями пусть ловит requireNonNull или @Nullable

Belkrr
()
Ответ на: комментарий от vbr

Как в компиляторе появится, так буду думать.

Компилятор - не единственное, что входит в экосистему языка. Уверен, что ты не через javac проекты собираешь.

Belkrr
()
Ответ на: комментарий от cocucka_B_TECTE

Ты предлагаешь из Java сделать Nullable Java. Выкинуть Optional за ненадобностью, разметить всю программу и полагаться на расширенную линтером систему типов. Я понимаю такой подход, но он противоречит всему, что пытаются делають из Java её разработчики. Если бы они хотели добавить nullable и non-nullable типы в язык, они бы просто это сделали. Это вроде не очень сложно. Но они этого не сделали и непосредственных планов на такое изменение вроде бы не имеют. Поэтому я всё же предпочитаю использовать язык так, как его задумали разработчики.

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

К примеру я видел, как люди просто везде засунули Optional. В поля, параметры, в общем везде. null у них вообще нигде не используется. Любое использование null по их стилю кодирования это ошибка (кроме передачи в библиотеки). Везде или requireNonNull или Optional.ofNullable. В принципе валидный подход и у них нормально работал. Тоже интересно. Но противоречит тому, как видят язык разработчики.

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 2)
Ответ на: комментарий от vbr

ВРЁТИ

package Test;

public class App {
    private static Integer i;

    public static Integer getI() {
        return i;
    }

    public static void main(String[] args)  {
        int i = getI();
    }
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "Test.App.getI()" is null
        at Test.App.main(App.java:11)
[{
	"resource": "/C:/Users/fake/eclipse-workspace/Test/Test/src/Test/Person.java",
	"owner": "_generated_diagnostic_collection_name_#3",
	"code": "67108964",
	"severity": 8,
	"message": "The method requireNonNull(String) is undefined for the type Person",
	"source": "Java",
	"startLineNumber": 10,
	"startColumn": 19,
	"endLineNumber": 10,
	"endColumn": 33
},{
	"resource": "/C:/Users/fake/eclipse-workspace/Test/Test/src/Test/Person.java",
	"owner": "_generated_diagnostic_collection_name_#3",
	"code": "16777235",
	"severity": 8,
	"message": "Type mismatch: cannot convert from Integer to Optional<Integer>",
	"source": "Java",
	"startLineNumber": 16,
	"startColumn": 38,
	"endLineNumber": 16,
	"endColumn": 41
},{
	"resource": "/C:/Users/fake/eclipse-workspace/Test/Test/src/Test/Person.java",
	"owner": "_generated_diagnostic_collection_name_#3",
	"code": "67108964",
	"severity": 8,
	"message": "The method requireNonNull(String) is undefined for the type Person.Builder",
	"source": "Java",
	"startLineNumber": 22,
	"startColumn": 44,
	"endLineNumber": 22,
	"endColumn": 58
},{
	"resource": "/C:/Users/fake/eclipse-workspace/Test/Test/src/Test/Person.java",
	"owner": "_generated_diagnostic_collection_name_#3",
	"code": "67108964",
	"severity": 8,
	"message": "The method requireNonNull(Integer) is undefined for the type Person.Builder",
	"source": "Java",
	"startLineNumber": 24,
	"startColumn": 42,
	"endLineNumber": 24,
	"endColumn": 56
},{
	"resource": "/C:/Users/fake/eclipse-workspace/Test/Test/src/Test/Person.java",
	"owner": "_generated_diagnostic_collection_name_#3",
	"code": "536870973",
	"severity": 4,
	"message": "The value of the local variable a is not used",
	"source": "Java",
	"startLineNumber": 29,
	"startColumn": 10,
	"endLineNumber": 29,
	"endColumn": 11,
	"tags": [
		1
	]
}]
Ygor ★★★★★
()

Нет, ты неправильно понял. Авторы жабы тут ничего не задумывали (можешь посмотреть историю опшенала).

Если кратко, то опшенал тебе нужно создавать по месту, чтобы избежать нулл проверок если работаешь с длинным чейном вызовов типа гет.гет.гет.гет ли например позволяют через мап.ор.ор.орЭлс явно записать способ получения значения без перетирания переменной, чего многие чекеры не любят.

Попытки использовать опшенал как-то иначе приводят к несаппортабельному коду.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от Ygor

По делу плиз пиши. Какое отношение твой код имеет к моему примеру? Такое ощущение, что галлюцинации ChatGPT наблюдаю.

PS поправил геттер, если тебе не очевидно было, что там подразумевался Optional.ofNullable

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 3)
Ответ на: комментарий от vbr

противоречит всему, что пытаются делають из Java её разработчики.

И

Это вроде не очень сложно.

Далеки от правды насколько это возможно.

Поддержка разнообразного туллинга вокруг языка и возможность писать в разных парадигмах точно в интересах разработчиков java.

А изменения в языке вроде valhalla растягиваются на десятилетия. (В valhalla, кстати есть и связанные с nullability вопросы)

Поэтому при выборе стиля стоит ориентироваться над более практичные вещи, чем «идеальный код в вакууме».

Может оказаться, что хорошая документация к библиотеке будет значительно полезнее, чем лучший из лучших подходов к nullability в ее интерфейсах.

Belkrr
()
Последнее исправление: Belkrr (всего исправлений: 1)
Ответ на: комментарий от Ygor

Ладно, распишу ВРЁТИ по пунктам.

Нет, это какая-то пародия на шаблон билдер в задумке потокобезопасный, но с такой реализацией нет.

Во-первых про потокобезопасность я не писал совсем ничего.

Во-вторых билдер потокобезопасным делать - ну можно, наверное, но это очень странная затея. Потокобезопасным делают сконструированный объект. И в данном примере он таки получился потокобезопасным. Если ты с этим не согласен - укажи подробней.

private Integer age;

Плюс распаковка пустой оболочки приведёт к nullpointerexception

В этом примере нет распаковки пустой оболочки. Она оборачивается в Optional и nullpointerexception там нигде не появится.

this.name = requireNonNull(name);

А это даже не скомпилится.

Вот исходный код стандартного метода Objects.requireNonNull:

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

Какие именно проблемы с компиляцией ты тут видишь?

Хочется клонов, то почему не реализовать интерфейс Cloneable и соотвествующий метод интерфейса?

Во-первых интерфейс Cloneable вообще не про это. И в современном коде почти нигде не используется.

Во-вторых это пустой интерфейс-маркер. У него нет никакого соответствующего метода. Вероятно ты имел в виду метод Object.clone.

В-третьих реализовывать его обычно не надо. Java его сама умеет реализовывать.

В-четвёртых это вообще был понятный минимальный пример, на котором я показал неудобство такого билдера.

Если говорить про то, как копирование надо делать на практике - надо сделать метод Person.update(), который вернёт инициализированный текущими значениями Builder и потом сделать пустой build(). Т.е. var personCopy = person.update().build(). Хотя для иммутабельного объекта смысла в таком действии немного.

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

По делу плиз пиши.

Челик, нахера ты в String лепишь final если стринг и так не изменяем? Нахера ты лепишь объектную оболочку вместо инта, но во внутреннем классе ты лепишь тоже самое но без final? Чтобы что? Ты скопировал откуда-то эту срань, не совсем понимая зачем она так тебе сделана?

Сомневаюсь, что ты понимаешь что ты делаешь, у тебя класс билдер никак не изменяет класс персон. Это не шаблон билдер, это рофл.

requireNonNull(name)

Пишу ему это так не работает, у него врёти.

Ygor ★★★★★
()
Ответ на: комментарий от Belkrr

Поддержка разнообразного туллинга вокруг языка и возможность писать в разных парадигмах точно в интересах разработчиков java.

То-то ломбок ломается практически каждый релиз.

Честно говоря не знаю, что там в интересах разработчиков Java. Какое-то взаимодействие с разработчиками у них, конечно, есть, то же mockito пока только страшные warning-и сыпет, но в целом по-моему у разработчиков Java есть план и они его придерживаются, а если тулинг этому плану не соответствует - тем хуже тулингу.

vbr ★★★★
() автор топика

В Idea есть предупреждения на Optional в параметрах метода и в поле, так что это я как бы не сам придумал. Ну и в статьях про это писали.

У меня ощущение, что Optional все же дрейфует в сторону полноценного контейнера.
Видел проекты, где Optional использовался во все поля (Optional properties, Optional arguments, etc) и все это отлично работало и код было легко понимать.

urxvt ★★★★★
()

В билдере могут быть не только примитивные (де)генеративные сеттеры атрибутов, но и более сложные, и тогда, внезапно получается …

Person new_person = new Person.Builder().load(old_person).build()

Optional тебе не нужен ибо твой Integer и так Nullable по постановке

В методе Builder.build() ты технически должен задекларировать throws NullPointerException (или в конструкторе Person(…)), но по логике лучше указать Person(@NotNull String name, Integer age)

no-dashi-v2 ★★★
()

Optional это функциональщина для потоков, по большей части она больше ни для чего не нужна и только забивает код барахлом.

Почему ты замарачиваешься обработкой null, но не замарачиваешься c обработкой age < 0? Если нужна обработка null, так явно её введи.


@Getter
@Builder(toBuilder = true)
class Person {
    private final String name;
    private final @Nullable Integer age;


    public boolean isPatric() {
        return age == null;
    }

    public Optional<Integer> safePatric() {
        return Optional.ofNullable(age);
    }
}


void clone(Person person) {
        Person васян = person.toBuilder().name("Васян").build();
        Person дед = person.toBuilder().age(100500).build();
        Person патрик = person.toBuilder().age(null).build();
}

«Почему ты замарачиваешься обработкой null, но не замарачиваешься c обработкой age < 0? » - Это ключевой вопрос. Вокруг этого null бегают и скачут, как будто это что-то такое особое, хотя и идея его подсветит null-говнокод, да и других стредств обработки вагон, а вот другие логические ошибки (иногда гораздо более важные) забывают и оставляют за бортом и получают тот же самый говнокод, но nullsafe. Думать надо про общую логику, а не про частный nullsafe.

vtVitus ★★★★★
()
Последнее исправление: vtVitus (всего исправлений: 2)

Похоже, что жаба превращается в плюсы по громоздкости конструкций. То же недо-ФП, тот же груз лишних конструкций, причем заточенных на частный случай

anonymous
()
Ответ на: комментарий от vbr

Я понимаю такой подход, но он противоречит всему, что пытаются делають из Java её разработчики.

Хера ты понимаешь, тогда чего такое задаёшь? Ты же в sharp умел? Или мне показалось? Что даёт эти warnings?

anonymous
()
Ответ на: комментарий от vtVitus

Думать надо про общую логику, а не про частный nullsafe.

Просто все полагаются на IDE. В Runtime стало часто валиться, так как прилетает null, а пацаны слишком умные. Вот и стали впиливать в ЯПы, чтобы не падало. А твоё Ктулху - это уже следующий уровень. Понимать нада!

anonymous
()

как-будто сами себя в рамки загоняете, а потом пытаетесь решить несуществующую проблему. Если оставаться в рамках идеи «аргумент метода не должен быть null», то сделайте:

// Person.Builder
// чтобы можно было сбрасывать возраст у копии
Person.Builder unknownAge() { 
  this.age = null;
  return this;
}

// чтобы цепочки не рвать
Person.Builder accept(Consumer<Person.Builder> builderConsumer) { 
  builderConsumer.accept(this);
  return this;
}

и:

// Person
// Пусть объект сам себя в Builder превращает
// null никуда за пределы класса не утечет
Person.Builder toBuilder() {
  var builder = new Person.Builder();
  builder.name = this.name;
  builder.age = this.age;
  return builder;
}
borisych ★★★★★
()

Я бы все это запихал просто в Record с Optional полями. Наверняка это никакой идеологии не противоречит, и не требует тонны поддерживающего кода.

maxcom ★★★★★
()
Ответ на: комментарий от ya-betmen

В большинстве проектов, что я видел, по коду размазана пляска из null-проверок, так как не понятно что нужно проверять а что нет. Потому, проверка чуть ли не через строку. @Nullable нормально никто не пользуется. Optional эту проблему решал просто отлично.

urxvt ★★★★★
()
Последнее исправление: urxvt (всего исправлений: 1)
Ответ на: комментарий от borisych

Кстати да, спасибо за пример, я жёстко протупил. Я-то билдер проектирую для создания объекта и изменение объекта не делаю. Но в общем случае оно будет нужно и, конечно, нужна необходимость обнулить поле. Т.е. только мой метод

    void age(Integer age) { this.age = requireNonNull(age); }

однозначно не подходит. Нужен или специальный метод очищения age (unknownAge или подобный), или просто принимать null тут, или принимать тут Optional.

Специальный метод вроде соотвествует описываемым принципам построения API (не принимать null и Optional в параметрах).

Принимать null - самый простой способ, не оказывает влияния на производительность.

Принимать Optional - даёт симметрию с геттером.

Есть над чем подумать…

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 3)

Если немного попробовать попредсказывать будущее, то в целом можно предполагать, что с введением Value-классов Optional станет именно таким и компилятор научится его оптимизировать так, будто это просто nullable объект.

В этом случае проблем с производительностью при хранении Optional в поле объекта больше возникать не будет.

Т.е. как предлагает maxcom - вариант с record у которого одно из полей Optional точно станет приемлемым, тем более, что там и деструктурирование красивое будет, ну отказываться от этого точно глупо.

Полагаю, что в этом случае описанный в первом сообщении код изменится на подход с хранением Optional-а в поле объекта. Геттер не изменится, а если вернуться к билдеру, ну тут логичней всего будет передавать Optional в сеттер.

Также планируется фича withers. Прямо сейчас record-ы я почти не использую, на мой взгляд они бесполезны для большинства случаев кроме тривиальных типа Point(int x, int y). У них нескрываемый публичный конструктор, это мне не совсем нравится. Использовать конструктор напрямую сложно - без именованных параметров код легко ломается - достаточно перепутать два String-а местами и никто не заметит. Ну и вообще эти record-ы для ADT делали, а не чтобы в них бизнес-данные маппить. С withers будет некая имитация именованных параметров, что, возможно, резко повысит юзабельность record-ов,

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)

Для интереса нашёл в JDK пример, похожий на мой код - класс HttpRequest (также есть похожий паттерн в классе HttpClient):

    public Optional<Duration> timeout() {
        return timeout == null ? Optional.empty() : Optional.of(timeout);
    }

...

    public HttpRequest.Builder timeout(Duration duration) {
        requireNonNull(duration);
        if (duration.isNegative() || Duration.ZERO.equals(duration))
            throw new IllegalArgumentException("Invalid duration: " + duration);
        this.duration = duration;
        return this;
    }

Что любопытно: у этого класса есть возможность создать Builder из готового HttpRequest, но возможности обнулить установленное поле timeout уже нет.

vbr ★★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от urxvt

Так я ж говорю, что если разрабы достаточно занудны, чтобы соблюдать это соглашение, то им опшенал в параметрах и полях не нужен.

ЗЫ. Вообще опшенал это характерная для новой жабы шляпа, которую делали без чёткого понимания кому и для чего она будет нужна.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от vbr

В этом случае проблем с производительностью при хранении Optional в поле объекта больше возникать не будет.

Если приглядеться, то у Optional отсутствует заветный extends Serializable, что очень часто мешает его делать полем объекта, а передавать Optional в аргументы метода мешает Type Erasure (здесь даже и не говорим о том, что конвенция - это всего лишь конвенция и никому в целом не мешает пихать или отдавать null вместо empty()). Ежели задаться вопросом, а чего это оно так, то можно найти довольно исчерпывающее объяснение: https://stackoverflow.com/a/24564612/3426309, что однако не мешает, разработчикам JDK писать откровенный .овнокод, jdk.internal.net вы уже привели в пример, в com.sun.tools.javac.comp Optional используется в качестве Lazy Supplier - довольно странное решение на мой взгляд

PS. как возвращаемое значение Optional тоже так себе: лаконичные цепочки получается построить крайне редко, а если еще и другой контейнер оборачивается, то совсем беда-беда

borisych ★★★★★
()
Последнее исправление: borisych (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

Так я ж говорю, что если разрабы достаточно занудны, чтобы соблюдать это соглашение, то им опшенал в параметрах и полях не нужен.

В любом случае нужен способ как-то пометить поле nullable. Иначе как узнать, нужна ли проверка на null или нет?

urxvt ★★★★★
()
Ответ на: комментарий от borisych

Если приглядеться, то у Optional отсутствует заветный extends Serializable, что очень часто мешает его делать полем объект

Учитывая то, что Serializable неформально deprecated этот аргумент не особо и аргумент.

а передавать Optional в аргументы метода мешает Type Erasure

Почему передавать List не мешает?

urxvt ★★★★★
()
Ответ на: комментарий от ya-betmen

Узнать на уровне логики приложения. Ты же не проверяешь постоянно все на null, так как ты понимаешь, что переменная Х не может быть null по дизайну, иначе это ошибка в ДКН^Wкоде.

urxvt ★★★★★
()
Ответ на: комментарий от urxvt

Ты же не проверяешь постоянно все на null

Так у тебя либо есть проверка аргументов на допустимые значения в т.ч. нулл или их нет.

А если не проверяешь, то как раз опшенал позволяет писать всё не заботясь о том нулл там пришёл или нет и с нулом приходится возиться только в небольшом процент случаев.

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

М... у меня ощущение, что мы друг друга не понимаем.

Так у тебя либо есть проверка аргументов на допустимые значения в т.ч. нулл или их нет.

При нормальных розкладах она должны быть только там где нужна. Например, формальный аргумент ф-ции помечен как @Nullable.
Если же в проекте не используется @Nullable (или Optional, как я говорю) во все поля, то тебе ковыряя код приходится гадать — а что мне с этим аргументом делать: проверять на null или нет?

А если не проверяешь, то как раз опшенал позволяет писать всё не заботясь о том нулл там пришёл или нет и с нулом приходится возиться только в небольшом процент случаев.

От типа приложения зависит, но во многих CRUDах и прочих автоматизаторах бизнес-процессов половина полей не обязательные (null).

urxvt ★★★★★
()
Ответ на: комментарий от urxvt

а передавать Optional в аргументы метода мешает Type Erasure

Почему передавать List не мешает?

Возможно, имеется в виду, что возникают проблемы с перегрузкой, в отличие от просто nullable-типа.

	public static void main (String[] args) {
		var a = Optional.of(Integer.valueOf(123));
		var b = Optional.of("foobar");
		print(a);
		print(b);
	}
 
	static void print(Optional<Integer> x) {
		System.out.println("opt'int: " + x);
	}
 
	static void print(Optional<String> x) {
		System.out.println("opt'string: " + x);
	}

=>

Main.java:22: error: name clash: print(Optional<String>) and print(Optional<Integer>) have the same erasure
	static void print(Optional<String> x) {
korvin_ ★★★★★
()
Ответ на: комментарий от urxvt

Зачем тебе что-то отдельно проверять на нулл если у тебя есть опшенал? Просто не нужно его в поля и аргументы тащить.

В 90% случаев работает принцип нулл на входе нулл на выходе. Оставшиеся 10 тебе в любом случае руками обрабатывать.

во многих CRUDах и прочих автоматизаторах бизнес-процессов половина

Да. И в жабе как раз почти все типы нуллабл

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

Зачем тебе что-то отдельно проверять на нулл если у тебя есть опшенал? Просто не нужно его в поля и аргументы тащить.

Так если он не прилетел в аргументе то как ты его родишь? Все аргументы в ofNullable() заворачивать?

В 90% случаев работает принцип нулл на входе нулл на выходе. Оставшиеся 10 тебе в любом случае руками обрабатывать.

Как узнать что null на входе?

urxvt ★★★★★
()