tag:blogger.com,1999:blog-14263979338975864602024-03-13T21:35:59.960-07:007ocb соображения по разработкеE.L.K.http://www.blogger.com/profile/17882024866007332780noreply@blogger.comBlogger3125tag:blogger.com,1999:blog-1426397933897586460.post-56186252905839343752015-08-11T01:38:00.000-07:002015-08-11T01:38:18.513-07:00Maybe<Maybe<Maybe<T>>>Многие языки имеют концепцию "нулевого указателя", унаследованную от близких к железкам языков. Ничего удивительного, что в языках вроде С/C++ где "объект" == "область памяти", nullptr, он же NULL, он же "нулевой указатель", был синонимом "отсутствия объекта".</br></br>
Многие языки более высокого уровня переняли это соответствие в том или ином виде: Java (null), Obj-C (nil), python (Nothing) и т.п.</br></br>
Проблема этого подхода в том, что возможность того, что возвращенное значение может быть nullptr - это часть устного контракта между написавшим и использующим метод. Например, может ли следующий код выбросить NullPointerException?
<pre><font color="#a020f0"><b>public</b></font> <font color="#228b22">void</font> <font color="#0000ff">doWork</font><font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>final</b></font> <font
На практике это неизвестно, т.к. завcolor="#228b22">String</font> <font color="#a0522d">some</font> <font color="#909090"><b>=</b></font> getSome<font color="#909090"><b>();</b></font>
<font color="#a020f0"><b>if</b></font> <font color="#909090"><b>(</b></font>some<font color="#909090"><b>.</b></font>isEmpty<font color="#909090"><b>())</b></font> <font color="#909090"><b>{</b></font>
doIfEmpty<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font> <font color="#a020f0"><b>else</b></font> <font color="#909090"><b>{</b></font>
doIfContainsSomething<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>}</b></font></pre>
На практике это неизвестно, т.к. зависит от деталей реализации метода getSome. Самая большая проблема в том, что даже если getSome никогда не возвращает null <u>сейчас</u>, нет никакой гарантии, что он не сделает этого в будущем.</br></br>
Важно понимать, что код <u>постоянно меняется и эволюционирует</u>. Это не так актуально для библиотечных проектов, которые имеют достаточно длительный жизненный цикл, однако для приложений которыми пользуется большое количество пользователей и которые активно развиваюся, ситуация "внезапно возникающего NPE", на мой взгляд, достаточно неприятна.</br></br>
Еще более интересно и неприятно обстоят дела на Obj-C, где разыменование nil не приводит к падению. </br></br>
Дело в том, что исправление ошибки тем проще (и дешевле!) чем раньше она обнаружена. В случае Obj-C ошибка разыменования nil может привести к странному поведению значительно позднее места своего возникновения. Например: семантика сравнения такова, что если !(A > B), и !(A < B), то A == B. Если у вас есть объект, определяющий, скажем, методы сравнения, то
<pre><font color="#909090"><b>[</b></font><font color="#008b8b">nil</font> <font color="#000090">isGreaterThan:</font>objectToTestWith<font color="#909090"><b>]</b></font> <font color="#909090"><b>==</b></font> <font color="#008b8b">nil</font> <font color="#aa7040">// </font><font color="#f4a460">i.e. false
</font><font color="#909090"><b>[</b></font><font color="#008b8b">nil</font> <font color="#000090">isLessThan:</font>objectToTestWith<font color="#909090"><b>]</b></font> <font color="#909090"><b>==</b></font> <font color="#008b8b">nil</font> <font color="#aa7040">// </font><font color="#f4a460">i.e. false</font>
<font color="#909090"><b>[</b></font><font color="#008b8b">nil</font> <font color="#000090">isEqualTo:</font>objectToTestWith<font color="#909090"><b>]</b></font> <font color="#909090"><b>==</b></font> <font color="#008b8b">nil</font> <font color="#aa7040">// </font><font color="#f4a460">i.e. false</font></pre>
То, есть, очевидно, что семантика сравнения нарушена. Само по себе это не проблематично, до тех пор, пока на основе таких выражений не строится дальнейшая логика. И что хуже, логика, побочные эффекты которой видны длительное время, например, код базы данных. А искать причину повреждения данных несколько дней - удовольствие из малоприятных, поэтому лучше избегать таких неочевидных вещей (и лучше пусть оно закрешится сразу и проблема будет быстро найдена и исправлена).</br></br>
Итак, что <b><u>же такое Maybe</u></b>.</br></br>
Maybe - это контейнерный тип, семантика которого такова, что он может содержать или не содержать значения. То есть мы выносим "возможно нет значения" на уровень системы типов вместо того, чтобы оставить его на уровне устного контракта между разработчиками.
Один из возможных способов определить этот тип выглядит так:</br></br>
<pre><font color="#a020f0"><b>abstract</b></font> <font color="#a020f0"><b>class</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>static</b></font> <font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> <font color="#0000ff">just</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">T</font> <font color="#a0522d">value</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> <font color="#228b22">boolean</font> isDefined<font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#008b8b">true</font><font color="#909090"><b>;</b></font>
<font color="#909090"><b>}</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> T get<font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> value<font color="#909090"><b>;</b></font>
<font color="#909090"><b>}</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> T or<font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">T</font> <font color="#a0522d">defaultValue</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> value<font color="#909090"><b>;</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>};</b></font>
<font color="#909090"><b>}</b></font>
<font color="#483d8b">@SuppressWarnings</font><font color="#909090"><b>(</b></font><font color="#906030">"unchecked"</font><font color="#909090"><b>)</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>static</b></font> <font color="#909090"><b><</b></font>T<font color="#909090"><b>></b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> nothing<font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> <font color="#228b22">boolean</font> isDefined<font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#008b8b">false</font><font color="#909090"><b>;</b></font>
<font color="#909090"><b>}</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> T get<font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>throw</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">RuntimeException</font><font color="#909090"><b>(</b></font><font color="#906030">"Value is not defined"</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> T or<font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">T</font> <font color="#a0522d">defaultValue</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> defaultValue<font color="#909090"><b>;</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>};</b></font>
<font color="#909090"><b>}</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>abstract</b></font> <font color="#228b22">boolean</font> <font color="#0000ff">isDefined</font><font color="#909090"><b>();</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>abstract</b></font> <font color="#228b22">T</font> <font color="#0000ff">get</font><font color="#909090"><b>();</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>abstract</b></font> <font color="#228b22">T</font> <font color="#0000ff">or</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">T</font> <font color="#a0522d">defaultValue</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font></pre>
И используется он таким образом:
<pre><font color="#a020f0"><b>final</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>></b></font> <font color="#a0522d">noString</font> <font color="#909090"><b>=</b></font> Maybe<font color="#909090"><b>.<</b></font>String<font color="#909090"><b>></b></font>nothing<font color="#909090"><b>();</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>></b></font> <font color="#a0522d">string</font> <font color="#909090"><b>=</b></font> Maybe<font color="#909090"><b>.</b></font>just<font color="#909090"><b>(</b></font><font color="#906030">"string"</font><font color="#909090"><b>);</b></font>
<font color="#a020f0"><b>if</b></font> <font color="#909090"><b>(</b></font>noString<font color="#909090"><b>.</b></font>isDefined<font color="#909090"><b>())</b></font> <font color="#909090"><b>{</b></font>
System<font color="#909090"><b>.</b></font>out<font color="#909090"><b>.</b></font>println<font color="#909090"><b>(</b></font><font color="#906030">"error!"</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font> <font color="#a020f0"><b>else</b></font> <font color="#909090"><b>{</b></font>
System<font color="#909090"><b>.</b></font>out<font color="#909090"><b>.</b></font>println<font color="#909090"><b>(</b></font><font color="#906030">"as expected noString is nothing."</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
System<font color="#909090"><b>.</b></font>out<font color="#909090"><b>.</b></font>println<font color="#909090"><b>(</b></font><font color="#906030">"string is: "</font> <font color="#909090"><b>+</b></font> string<font color="#909090"><b>.</b></font>get<font color="#909090"><b>());</b></font>
System<font color="#909090"><b>.</b></font>out<font color="#909090"><b>.</b></font>println<font color="#909090"><b>(</b></font><font color="#906030">"alternative of string is: "</font> <font color="#909090"><b>+</b></font> string<font color="#909090"><b>.</b></font>or<font color="#909090"><b>(</b></font><font color="#906030">"should not be printed"</font><font color="#909090"><b>));</b></font>
System<font color="#909090"><b>.</b></font>out<font color="#909090"><b>.</b></font>println<font color="#909090"><b>(</b></font><font color="#906030">"alternative of noString is: "</font> <font color="#909090"><b>+</b></font> noString<font color="#909090"><b>.</b></font>or<font color="#909090"><b>(</b></font><font color="#906030">"alternative"</font><font color="#909090"><b>));</b></font></pre>
Существуют библиотеки, имеющие реализацию похожего типа, например тип <a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html">Optional в guava</a>. Тип конвертера также имеется в этой библиотеке.</br></br>
Первый и самый важный плюс, это <u><b>явно выраженное намерение</b></u>.</br></br>
Как известно многим (увы не всем), код пишется не для машины. Код пишется <u>для людей</u>. И чем точнее и недвусмысленнее основная идея, заложенная в код, будет видна читающему или пользующемуся кодом в дальнейшем, тем лучше.</br></br>
Без использования Maybe, этот метод может возвращать либо объект, либо null:
<pre><font color="#a020f0"><b>public</b></font> <font color="#228b22">String</font> <font color="#0000ff">getSome</font><font color="#909090"><b>();</b></font></pre>
Однако, если использовать Maybe, то метод с такой сигнатурой всегда должен вернуть объект. Чтобы иметь возможность вернуть "отсутствие объекта" нужно явно поменять сигнатуру на:
<pre><font color="#a020f0"><b>public</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>></b></font> <font color="#0000ff">getSome</font><font color="#909090"><b>();</b></font></pre>
Из новой сигнатуры четко видно, что возможна ситуация когда объекта просто нет.</br></br>
Следующий плюс, это <u><b>возможность раннего обнаружения ошибок</b></u>.</br></br>
Если метод getSome, как уже было сказано выше, не возвращает null, но после некоторых внутренних модификаций начинает, то весь код, который работал ранее в предположении что getSome всегда возвращает объект (предположение было верно в тот момент когда этот код писался), оказывается сломан. И что самое плохое, сломан втихую, то есть это не будет обнаружено до тех пор пока getSome не вернет null для каждого места где это критично.</br></br>
С другой стороны, как было показано выше, в случае если метод начинает возвращать Maybe<String> для того, чтобы выразить факт того, что объекта может не быть, весь код, который ранее вызывал этот метод, будет сломан явно, то есть компилятор просто не даст такому коду собраться.</br></br>
Действительно, в изначальном примере:
<pre><font color="#a020f0"><b>public</b></font> <font color="#228b22">void</font> <font color="#0000ff">doWork</font><font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">some</font> <font color="#909090"><b>=</b></font> getSome<font color="#909090"><b>();</b></font>
<font color="#a020f0"><b>if</b></font> <font color="#909090"><b>(</b></font>some<font color="#909090"><b>.</b></font>isEmpty<font color="#909090"><b>())</b></font> <font color="#909090"><b>{</b></font>
doIfEmpty<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font> <font color="#a020f0"><b>else</b></font> <font color="#909090"><b>{</b></font>
doIfContainsSomething<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>}</b></font> </pre>
Строка
<pre><font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">some</font> <font color="#909090"><b>=</b></font> getSome<font color="#909090"><b>();</b></font></pre>
Перестанет компилироваться как только сигнатура метода getSome изменится. Это даст возможность тому, кто внес изменения в getSome, корректно исправить поведение всех мест, где getSome используется.</br></br>
Следующий плюс это то, что, поскольку Maybe всегда не null, у него <u><b>всегда можно вызывать методы</b></u>.</br></br>
С одной стороны
<pre><font color="#a020f0"><b>public</b></font> <font color="#228b22">void</font> <font color="#0000ff">doWork</font><font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>></b></font> <font color="#a0522d">some</font> <font color="#909090"><b>=</b></font> getSome<font color="#909090"><b>();</b></font>
<font color="#a020f0"><b>if</b></font> <font color="#909090"><b>(</b></font>some<font color="#909090"><b>.</b></font>value<font color="#909090"><b>().</b></font>isEmpty<font color="#909090"><b>())</b></font> <font color="#909090"><b>{</b></font>
doIfEmpty<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font> <font color="#a020f0"><b>else</b></font> <font color="#909090"><b>{</b></font>
doIfContainsSomething<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>}</b></font></pre>
Вызов value() без проверки isValue() все равно приведет к падению. И это, увы, неизбежно. Но, это не единственный способ использовать значение. Например, если добавить в систему интерфейс сравнения, то то, что ранее требовало кучи кода, сравнение двух потенциально нулевых объектов, теперь можно выразить одной строчкой. Привожу больше кода, но сравнение тут только последняя строка:
<pre><font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>interface</b></font> <font color="#228b22">Comparator</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> <font color="#909090"><b>{</b></font>
<font color="#228b22">boolean</font> <font color="#0000ff">isEquals</font><font color="#909090"><b>(</b></font><font color="#228b22">T</font> <font color="#a0522d">l</font><font color="#909090"><b>,</b></font> <font color="#228b22">T</font> <font color="#a0522d">r</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>final</b></font> <font color="#a020f0"><b>class</b></font> <font color="#228b22">Comparsions</font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Comparator</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>></b></font> <font color="#a0522d">COMPARE_STRINGS_CASE_SENSITIVE</font> <font color="#909090"><b>=</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">Comparator</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#aa7040">// </font><font color="#f4a460">comparator implementation
</font> <font color="#909090"><b>}</b></font> <font color="#909090"><b>;</b></font>
<font color="#909090"><b>}</b></font> </pre>
Также это позволяет использовать значение по умолчанию таким образом:
<pre>textView<font color="#909090"><b>.</b></font>setText<font color="#909090"><b>(</b></font>something<font color="#909090"><b>.</b></font>or<font color="#909090"><b>(</b></font><font color="#906030">""</font><font color="#909090"><b>));</b></font></pre>
Этот подход хорош тем, что он позволяет обойтись без проверок тогда, когда логически эти проверки не нужны. Например, имея интерфейс конвертирования, можно преобразовать один Maybe в другой без разворачивания значения:
<pre><font color="#a020f0"><b>interface</b></font> <font color="#228b22">Converter</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>,</b></font> <font color="#228b22">F</font><font color="#909090"><b>></b></font> <font color="#909090"><b>{</b></font>
<font color="#228b22">T</font> <font color="#0000ff">convert</font><font color="#909090"><b>(</b></font><font color="#228b22">F</font> <font color="#a0522d">f</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">String</font><font color="#909090"><b>></b></font> <font color="#a0522d">maybeString</font> <font color="#909090"><b>=</b></font> <font color="#aa7040">/* </font><font color="#f4a460">... */</font><font color="#909090"><b>;</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Maybe</font><font color="#909090"><b><</b></font><font color="#228b22">Integer</font><font color="#909090"><b>></b></font> <font color="#a0522d">strlen</font> <font color="#909090"><b>=</b></font> maybeString<font color="#909090"><b>.</b></font>convert<font color="#909090"><b>(</b></font>GET_STRING_LENGHT<font color="#909090"><b>);</b></font></pre>
Конечно, использование типа Maybe (или Optional, или аналога), делет код несколько более многословным, однако преимущества, которые это дает, окупают эти сложности полностью.
E.L.K.http://www.blogger.com/profile/17882024866007332780noreply@blogger.com0tag:blogger.com,1999:blog-1426397933897586460.post-27961153635636452172015-08-07T03:39:00.002-07:002015-08-07T03:39:21.405-07:00Android View visibility DSLЧасто встречаю такой код при разработке для Android:
<pre>view<font color="#909090"><b>.</b></font>setVisibility<font color="#909090"><b>(</b></font>condition ? <font color="#008b8b">View</font><font color="#909090"><b>.</b></font>VISIBLE <font color="#909090"><b>:</b></font> <font color="#008b8b">View</font><font color="#909090"><b>.</b></font>GONE<font color="#909090"><b>);</b></font></pre>
Использование такой конструкции приводит к тому, что это тернарное выражение дублируется в setVisibility практически каждого view, который нужно показать или спрятать.
В качестве альтернативы можно использовать простенький DSL, который позволит писать что то вроде:
<pre>gone<font color="#909090"><b>(</b></font>view<font color="#909090"><b>,</b></font>
otherView<font color="#909090"><b>,</b></font>
moreView<font color="#909090"><b>)</b></font>
<font color="#909090"><b>.</b></font>orVisibleIf<font color="#909090"><b>(</b></font>condition<font color="#909090"><b>);</b></font></pre>
Плюсов сразу несколько -это позволяет управлять видимостью нескольких view одновременно, убирает многократное повторение условного выражения и в целом короче и лучше читается.
А вот "определение" этого DSL:
<pre><font color="#a020f0"><b>final</b></font> <font color="#a020f0"><b>class</b></font> <font color="#228b22">ViewVisibilityTools</font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>interface</b></font> <font color="#228b22">OrVisible</font> <font color="#909090"><b>{</b></font>
<font color="#228b22">void</font> <font color="#0000ff">orVisibleIf</font><font color="#909090"><b>(</b></font><font color="#228b22">boolean</font> <font color="#a0522d">condition</font><font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#228b22">OrVisible</font> <font color="#0000ff">gone</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">View</font><font color="#909090"><b>...</b></font><font color="#a0522d">views</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">OrVisible</font><font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> <font color="#228b22">void</font> orVisibleIf<font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">boolean</font> <font color="#a0522d">condition</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
setVisible<font color="#909090"><b>(</b></font>views<font color="#909090"><b>,</b></font> condition<font color="#909090"><b>,</b></font> <font color="#008b8b">View</font><font color="#909090"><b>.</b></font>GONE<font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>};</b></font>
<font color="#909090"><b>}</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#228b22">OrVisible</font> <font color="#0000ff">invisible</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">View</font><font color="#909090"><b>...</b></font><font color="#a0522d">views</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">OrVisible</font><font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> <font color="#228b22">void</font> orVisibleIf<font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">boolean</font> <font color="#a0522d">condition</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
setVisible<font color="#909090"><b>(</b></font>views<font color="#909090"><b>,</b></font> condition<font color="#909090"><b>,</b></font> <font color="#008b8b">View</font><font color="#909090"><b>.</b></font>INVISIBLE<font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>};</b></font>
<font color="#909090"><b>}</b></font>
<font color="#a020f0"><b>private</b></font> <font color="#228b22">void</font> <font color="#0000ff">setVisible</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">View</font> <font color="#a0522d">views</font><font color="#909090"><b>[],</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">boolean</font> <font color="#a0522d">condition</font><font color="#909090"><b>,</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">int</font> <font color="#a0522d">alternative</font><font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">int</font> <font color="#a0522d">visibility</font><font color="#909090"><b>;</b></font>
<font color="#a020f0"><b>if</b></font> <font color="#909090"><b>(</b></font>condition<font color="#909090"><b>)</b></font> visibility <font color="#909090"><b>=</b></font> <font color="#008b8b">View</font><font color="#909090"><b>.</b></font>VISIBLE<font color="#909090"><b>;</b></font>
<font color="#a020f0"><b>else</b></font> visibility <font color="#909090"><b>=</b></font> alternative<font color="#909090"><b>;</b></font>
<font color="#a020f0"><b>for</b></font> <font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">View</font> <font color="#a0522d">v</font><font color="#909090"><b>:</b></font> views<font color="#909090"><b>)</b></font> <font color="#909090"><b>{</b></font>
v<font color="#909090"><b>.</b></font>setVisibility<font color="#909090"><b>(</b></font>visibility<font color="#909090"><b>);</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>}</b></font></pre>
Нужно только статически импортировать методы gone и invisible там где они нужны.
E.L.K.http://www.blogger.com/profile/17882024866007332780noreply@blogger.com0tag:blogger.com,1999:blog-1426397933897586460.post-12036186686956036502015-03-09T01:57:00.003-07:002015-03-09T05:16:27.526-07:00API тесты, тестирование парсеров и toStringМы живем в несовершенном мире. В совершенном мы имеем возможность получить полную спецификацию API, написать по ней тесты и потом написать парсеры для ответов сервера, опираясь на тесты.<br>
<br>
Но мир несовершенен и поэтому можно столкнуться с ситуацией когда есть 100500 кода, парсящего ответы от сервера, написанного не очень хорошо, большая часть давно и многое людьми которые уже ушли.<br>
<br>
Есть два варианта - вздохнуть и оставить все как есть или пытаться привести эту механику в порядок.<br>
<br>
Однако, для того, чтобы привести ее в порядок нужно быть уверенным что ничего не сломается. Ну или хотя бы минимизировать этот шанс насколько это возможно разумными усилиями. Для этой цели хорошо подходят тесты. Для каждого парсера можно заготовить один или несколько тестов такого вида:<br>
<br>
<pre><font color="#a020f0"><b>public</b></font> <font color="#228b22">void</font> <font color="#0000ff">testParse</font><font color="#909090"><b>()</b></font> <font color="#a020f0"><b>throws</b></font> <font color="#228b22">Exception</font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">input</font> <font color="#909090"><b>=</b></font> <font color="#906030">"some, for example, json"</font><font color="#909090"><b>;</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">ExpectedResult</font> <font color="#a0522d">expected</font> <font color="#909090"><b>=</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">ExpectedResult</font><font color="#909090"><b>(</b></font><font color="#aa7040">/* </font><font color="#f4a460">... */</font><font color="#909090"><b>);</b></font>
assertEquals<font color="#909090"><b>(</b></font>expected<font color="#909090"><b>,</b></font>
ParserUnderTest<font color="#909090"><b>.</b></font>parse<font color="#909090"><b>(</b></font>input<font color="#909090"><b>));</b></font>
<font color="#909090"><b>}</b></font></pre><br><br>
Но вот беда - для тестов нужен образец, с которым сравнивать результат.<br><br>
Хорошо если объекты небольшие и их немного. В противном случае, если объекты содержат списки объектов, которые, потенциально, содержат списки объектов и т.п. - извлечение данных и ручное конструирование объектов-образцов может занять достаточно продолжительное время, к тому же может оказаться довольно занудной процедурой.
Но зачем делать самому то, что может сделать машина?<br><br>
В java у объектов есть метод toString. Согласно документации, этот метод<br><br>
<blockquote>Returns a string representation of the object. In general, the toString method returns a string that "textually represents" this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method. </blockquote><br><br>
Любопытно то, что результат должен быть "easy for person to read". Но мы можем дополнительно наложить ограничение чтобы он был "easy for machine to parse". Когда я столкнулся с такой задачей, то я принял следующий формат: объект в круглых скобках, список в квадратных, отображение (map) в фигурных. Получается что то вида:<br><br>
<pre>(ClassName field=(OtherClass field="string" intField=1))
[(ClassName field=null), (ClassName field=null)]</pre><br><br>
Очевидно, что такой формат подпадает под оба критерия - он легко может быть разобран машиной и в то же время не теряет человекочитаемости.<br><br>
Для формирования такого вывода удобно определить класс с интерфейсом вроде такого:
<pre><font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>final</b></font> <font color="#a020f0"><b>class</b></font> <font color="#228b22">ToStringBuilder</font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#0000ff">ToStringBuilder</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">className</font><font color="#909090"><b>);</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#0000ff">ToStringBuilder</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">Object</font> <font color="#a0522d">o</font><font color="#909090"><b>);</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> <font color="#228b22">ToStringBuilder</font> <font color="#0000ff">field</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">name</font><font color="#909090"><b>,</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">List</font><font color="#909090"><b><</b></font><font color="#228b22">T</font><font color="#909090"><b>></b></font> <font color="#a0522d">o</font><font color="#909090"><b>);</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#909090"><b><</b></font><font color="#228b22">K</font><font color="#909090"><b>,</b></font> <font color="#228b22">V</font><font color="#909090"><b>></b></font> <font color="#228b22">ToStringBuilder</font> <font color="#0000ff">field</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">name</font><font color="#909090"><b>,</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Map</font><font color="#909090"><b><</b></font><font color="#228b22">K</font><font color="#909090"><b>,</b></font> <font color="#228b22">V</font><font color="#909090"><b>></b></font> <font color="#a0522d">o</font><font color="#909090"><b>);</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#228b22">ToStringBuilder</font> <font color="#0000ff">field</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">name</font><font color="#909090"><b>,</b></font>
<font color="#a020f0"><b>final</b></font> <font color="#228b22">Object</font> <font color="#a0522d">o</font><font color="#909090"><b>);</b></font>
<font color="#a020f0"><b>public</b></font> <font color="#228b22">String</font> <font color="#0000ff">toString</font><font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font></pre><br><br>
В таком случае реализация метода toString для каждого класса API представляется довольно тривиальной:<br><br>
<pre><font color="#a020f0"><b>public</b></font> <font color="#a020f0"><b>final</b></font> <font color="#a020f0"><b>class</b></font> <font color="#228b22">SomeApiClass</font> <font color="#909090"><b>{</b></font>
<font color="#aa7040">/* </font><font color="#f4a460">... */</font>
<font color="#a020f0"><b>private</b></font> <font color="#a020f0"><b>final</b></font> <font color="#228b22">int</font> <font color="#a0522d">mIntField</font><font color="#909090"><b>;</b></font>
<font color="#a020f0"><b>private</b></font> <font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">mStringField</font><font color="#909090"><b>;</b></font>
<font color="#483d8b">@Override</font> <font color="#a020f0"><b>public</b></font> String toString<font color="#909090"><b>()</b></font> <font color="#909090"><b>{</b></font>
<font color="#a020f0"><b>return</b></font> <font color="#a020f0"><b>new</b></font> <font color="#228b22">ToStringBuilder</font><font color="#909090"><b>(</b></font><font color="#a020f0"><b>this</b></font><font color="#909090"><b>)</b></font>
<font color="#909090"><b>.</b></font>field<font color="#909090"><b>(</b></font><font color="#906030">"mIntField"</font><font color="#909090"><b>,</b></font> mIntField<font color="#909090"><b>)</b></font>
<font color="#909090"><b>.</b></font>field<font color="#909090"><b>(</b></font><font color="#906030">"mStringField"</font><font color="#909090"><b>,</b></font> mStringField<font color="#909090"><b>)</b></font>
<font color="#909090"><b>.</b></font>toString<font color="#909090"><b>();</b></font>
<font color="#909090"><b>}</b></font>
<font color="#909090"><b>}</b></font></pre><br><br>
более того, в языках где существует reflection, можно использовать его для автоматического вывода объектов.<br><br>
Таким образом, у нас есть возможность получить строковое представление, которое затем можно распарсить и из распаршеного представления сгенерировать объект-образец.<br><br>
Тут также возможно несколько путей - строить объекты с помощью reflection'а же или просто сгенерировать код, создающий требуемые объекты. Я пошел по последнему пути, так как это делает код тестов легче поддерживаемым. <br><br>
Аналогично можно по разному осуществлять сравнение объектов, однако, как выяснилось, самым простым способом было сравнивать строковые представления объектов. То есть:<br><br>
<pre>assertEquals<font color="#909090"><b>(</b></font>expected<font color="#909090"><b>.</b></font>toString<font color="#909090"><b>(),</b></font> parsed<font color="#909090"><b>.</b></font>toString<font color="#909090"><b>());</b></font></pre><br><br>
Собирая все воедино: <br><br>
- Для каждого участвующего класса реализуется метод toString.
- Код, обрабатывающий ответ сервера, дополняется кодом записывающим в файл ответ сервера и строковое представление распаршенного объекта ответа.<br>
- Создается утилита-парсер (это долгосрочное вложение - такая утилита может легко использоваться повторно, к тому же такая утилита достаточно проста - реализация на языке Haskell заняла у меня около 80 строк), которая парсит из файла из п1 пару "тело ответа"-"объект образец" и генерирует код теста такого вида, как был представлен в начале статьи - все данные для этого уже есть на этом этапе. <br><br>
В результате из дампа вида: <br><br>
<pre>---------- request:
some magic json!
---------- response:
[(ApiObject id=4242, name="magicName"), (ApiObject id=2223, name="othername")]</pre><br><br>
Можно автоматизированно получить код для теста вида:<br><br>
<pre><font color="#a020f0"><b>final</b></font> <font color="#228b22">String</font> <font color="#a0522d">input</font> <font color="#909090"><b>=</b></font> <font color="#906030">"some magic json!"</font><font color="#909090"><b>;</b></font>
assertEquals<font color="#909090"><b>(</b></font>Arrays
<font color="#909090"><b>.</b></font>asList
<font color="#909090"><b>(</b></font><font color="#a020f0"><b>new</b></font> <font color="#228b22">ApiObject</font><font color="#909090"><b>(</b></font><font color="#00008b"><b>4242</b></font><font color="#909090"><b>,</b></font>
<font color="#906030">"magicName"</font><font color="#909090"><b>),</b></font>
<font color="#a020f0"><b>new</b></font> <font color="#228b22">ApiObject</font><font color="#909090"><b>(</b></font><font color="#00008b"><b>2223</b></font><font color="#909090"><b>,</b></font>
<font color="#906030">"othername"</font><font color="#909090"><b>)).</b></font>toString<font color="#909090"><b>(),</b></font>
PARSER<font color="#909090"><b>.</b></font>parse<font color="#909090"><b>(</b></font>input<font color="#909090"><b>).</b></font>toString<font color="#909090"><b>());</b></font></pre><br><br>
Таким образом легко подготовить тесты для парсеров объектов ответа практически любой сложности и при этом время, необходимое на то, чтобы сгенерировать такие тесты очень слабо зависит от сложности объектов.<br><br>
<b>Upd:</b> Кстати, да, есть вариант реализовать toString таким образом, чтобы он сразу генерировал код создания, без промежуточного шага с парсером. Однако это ломает человекочитаемость вывода toString.<br><br>
И, как показала практика, сравнивать строками годится только в случае простых объектов - этот способ может не работать при использовании HashMap и HashSet. Так что для более сложных объектов придется использовать reflection-based решение для сравнения в тестах или писать код equals вручную. E.L.K.http://www.blogger.com/profile/17882024866007332780noreply@blogger.com0