Показаны сообщения с ярлыком code style. Показать все сообщения
Показаны сообщения с ярлыком code style. Показать все сообщения

19 июля 2020 г.

Design of everyday things

Недавно прочитал "Дизайн привычных вещей" Дональда Нормана. Норман – computer scientist, и когнитивный психолог, известный и авторитетный специалист по эргономике. Он популяризовал сам термин "user experience", и был, вероятно, первым User Experience Architect – в Apple, что совершенно не удивляет.

На его книгу я наткнулся совершенно случайно, и залип с первых страниц. Книга написана в 1986 году, и Норман в предисловии пишет, что сознательно не переписывал разделы про кнопочные телефоны и видеомагнитофоны – технику того времени, давно уже канувшую в лету. Я немного опасался, что читать про проблемы интерфейса вещей, которые уже 20 лет никем не используются, будет скучно – но нет, наоборот. Вещи из 80-х проще, и ошибки дизайна на их примере понятнее – и но совершенно очевидно, что современные дизайнеры, проектируя гораздо более сложные вещи, продолжают делать ровно такие же ошибки.

2 февраля 2016 г.

Массовые расстрелы и духовная практика смирения


— Микола, ты слыхал как профессиональные программисты тм наш newValue кличут?
— Не, а как?
— "о"!
— Да ну! Вот же ж нелюди, поубивал бы гадов!

Разбирался сегодня в одном из наших сервисов, который использует довольно старую версию trove (2.1.0)

28 июля 2012 г.

Синтаксический сахарочек

Вообще, java нас не балует синтаксическим сахаром. Лично мне это даже очень нравится — терпеть не могу выкапывать суть под горой сладкого. Из-за этого отказался от мысли перейти на Scala — побоялся диабет схватить. Но иногда хочется все-таки сделать своему компоненту такой сладенький интерфейс, чтобы пальчики оближешь. И когда я нахожу в аскетической джаве какую-нибудь карамельку — я очень радуюсь :)

Например, есть у нас такой класс-мэппер
public class Mapping{
  public String map(final String arg1, final String arg2);
}
И есть какая-то реализация, скажем SimpleImmutableMapping, и хочется сделать для его конфигурирования фабрику-билдер, типа вот такой:
public class MappingBuilder{
    public MappingBuilder map( final String arg1 ){...}
    public MappingBuilder with( final String arg2 ){...}
    public MappingBuilder to( final String mapping ){...}

    public Mapping build(){...}
}
...
final Mapping mapping = new MappingBuilder()
                           .map( "A" ).with( "B" ).to("ab")
                           .map( "C" ).with( "D" ).to("cd")
                           .map( "E" ).with( "F" ).to("ef")
                           .build();
Что мне здесь не нравится? — мне не нравится дуракоустойчивость. Особенно мне не нравится то, что ее здесь нет, и это хорошо видно на скриншоте справа (Да, это у меня такая цветовая схема, нет, глаза не устают, да, мне нравится, идите в жопу).
Что будет, если я вызову в этой точке .build()? Очевидно, ничего хорошего. В лучшем случае уже во время выполнения вылетит IllegalStateException("Can't build mapping with supplied data: first argument is not enough"). Это если я позаботился о том, как мой объект себя ведет, если его используют "неправильно". В более реальном случае будет что-нибудь вроде NullPointerException из недр метода .build(). В самом паршивом случае здесь все отработает без ошибок, а веселье начнется при использовании созданного Mapping-а где-нибудь совсем в другом месте.

Так вот, я раньше думал, что устроить в подобном Builder-е проверку времени выполнения — на яве не получится. А оказывается можно — правда так гемморойно как и все у нас, джавистов:
public class SimpleMapping {

  //код самого мэппера -- скучный и банальный
  .....

  //а вот дальше начинается треш и содомия
  //уберите от монитора женщин и детей, и глубоко вдохните 

  public interface FirstArgumentGivenState {
    public SecondArgumentGivenState with( final String arg2 );
  }

  public interface SecondArgumentGivenState {
    public FullMappingGivenState to( final String mapping );
  }

  public interface FullMappingGivenState {
    public Mapping build();
    public FirstArgumentGivenState map( final String arg1 );
  }

  private static class Builder implements
      FirstArgumentGivenState,
      SecondArgumentGivenState,
      FullMappingGivenState {

    public FirstArgumentGivenState map( final String arg1 ) {
      ...
      return this;
    }

    public SecondArgumentGivenState with( final String arg2 ) {
      ...
      return this;
    }

    public FullMappingGivenState to( final String mapping ) {
      ...
      return this;
    }

    public Mapping build() {
      ...
    }
  }

  public static FullMappingGivenState builder() {
    return new Builder();
  }
}
....
final Mapping mapping = SimpleMapping.builder()
    .map( "A" ).with( "B" ).to( "ab" )
    .map( "C" ).with( "D" ).to( "cd" )
    .map( "E" ).with( "F" ).to( "ef" )
    .build();
Попробуйте теперь вызвать что-то неправильно :)

По названию интерфейсов можно понять, как эта конструкция масштабируется на более сложные системы — представляем Builder в виде конечного автомата, на каждое состояние заводим интерфейс в котором методы, соответствующие разрешенным переходам из этого состояния. И private класс-реализация реализует все эти интерфейсы.


По-правде, я не уверен, что придумать такое было хорошей идеей — встреть я такой адъ в чужом коде, матерился бы в голос. А ведь самому-то теперь будет сложно удержаться :)

Если серьезно, то мне кажется, делать такое в каждом Builder-е это over-engeneering. Сложность понимания "как, и зачем, черт подери, такое здесь наворочено" не окупает небольшого увеличения дуракоустойчивости. Но вот если какая-нибудь изолированная, часто используемая библиотека... Тогда к странным типам возвращаемых значений у методов Builder-а можно и привыкнуть, а вот compile time safety при широком использовании может вполне себя окупить.

Если что — похороны за ваш счет. Я предупреждал :)

UPD:Как мне подсказывает Александр в комментариях — это уже описано как паттерн Wizard. Было бы слишком оптимистично ожидать, что такую штуку еще никто не придумал. Статью рекомендую прочитать, примеры там забавные.

3 августа 2011 г.

Всю свою жизнь я писал статические классы-утилиты таким образом:
public final class Helpers{
  private Helpers(){
  }
  public static String veryHelpfullMethod(){
  ....
  }
}

Недавно заметил у коллеги в коде такой вариант:
public final class Helpers{
  private Helpers(){
    throw new AssertionError("Not for instantiation!");
  }
}


Взял себе на вооружение. И правда, с нынешним reflection-ом закрытый конструктор уже не гарантирует, что класс невозможно будет создать. А вот так -- final-класс, с единственным конструктором и выбросом исключения -- я уже не могу придумать способа (кроме byte-code-modification, конечно -- но против лома нет приема).

6 июля 2010 г.

Никогда, никогда, никогда блядь не пишите так:
public MyObject(final int param){    
    if( param < 0 ){        
        throw new IllegalArgumentException("Incorrect param");   
    }
}
надо как минимум вот так:
public MyObject(final int param){    
    if( param < 0 ){
        throw new IllegalArgumentException("Incorrect param: must be >=0, but got "+param);
    }
}
Это всего на пару дюжин символов длиннее -- но знали бы вы, сколько времени это иногда может сэкономить при отладке... Собственно, часто это может сделать саму отладку просто не нужной -- будет сразу понятно, где возник косяк. Особенно обидно, что такие вещи часто встречаются в самом jdk -- класс Color тому наглядный пример. В свое время в Матконструкторе мы частенько создавали Color из javascript, и когда получаешь "Color parameter outside of expected range: Alpha" это ни разу не помогает что-либо понять.