Увеличиваем читаемость данных
Совсем плохо:
private static final Set<String> INTERESTING_TAGS =
new HashSet<String>();
Map<String,Integer> WEIGHTS = new HashMap<String,Integer>
static {
INTERESTING_TAGS.add("A");
INTERESTING_TAGS.add("FORM");
INTERESTING_TAGS.add("INPUT");
INTERESTING_TAGS.add("SCRIPT");
INTERESTING_TAGS.add("OBJECT");
WEIGHTS.put("bad", -2);
WEIGHTS.put("poor", -1);
WEIGHTS.put("average", 0);
WEIGHTS.put("nice", 1);
WEIGHTS.put("outstanding", 3);
}
|
Плохо:
private static final Set<String> INTERESTING_TAGS =
new HashSet<String>(Arrays.asList(new String[]
{
"A","FORM","INPUT","SCRIPT","OBJECT"
}));
for(int coinValue : new int[] {1, 2, 5, 10, 20, 50, 100})
{
...
}
|
Хорошо:
public class CollectionUtils
{
public static T[] ar(T... ts) {return ts;}
public static Set<T> set(T... ts)
{
return new HashSet<T>(Arrays.asList(ts));
}
public static Map<K,V> zipMap(K[] keys, V[] values) {...}
}
import static CollectionUtils.set;
import static CollectionUtils.ar;
private static final Set<String> INTERESTING_TAGS =
set("A","FORM","INPUT","SCRIPT","OBJECT");
for(int coinValue : ar(1, 2, 5, 10, 20, 50, 100)) {
...
}
Map<String,Integer> WEIGHTS = zipMap(
ar("bad", "poor", "average", "nice", "outstanding"),
ar(-2, -1, 0, 1, 3));
|
Казалось бы, как просто! Обыкновенная абстракция конструкторов данных, вторая глава классической библии программирования «Структура и интерпретация компьютерных программ» Абельсона и Сассмана.
Особенно ярко полезность упрощенного синтаксиса для создания данных проявляется в юнит-тестах, когда есть необходимость сконструировать для программы множество входных примеров.
В этом случае очень часто оправдано даже кодирование структур в строке и написание маленького парсера. Например:
assertTrue(GraphAnalyzer.isConnected(graph("1->2 2->3 3->1")));
|
Легко представить себе, как выглядела бы эта строчка без абстракции конструктора – последовательность конструкторов, объявлений переменных и вызовов вида .addVertex() и .addEdge().
Отсутствие объявляемых временных переменных, засоряющих область видимости – одно из основных преимуществ этих приемов.
Увеличиваем читаемость комбинаторов
Плохо:
Filter f = new AndFilter(first, second);
|
Хорошо:
public abstract class Filters
{
public static Filter and(Filter a, Filter b) {return new AndFilter(a,b);}
}
import static Filters.*;
Filter f = and(first,second);
|
Еще лучше:
public abstract class Filter
{
Filter and(Filter other) {return Filters.and(this,other);}
}
Filter f = first.and(second).and(third);
|
Совсем хорошо:
public abstract class Filters
{
public static Filter ALWAYS_TRUE = new AlwaysTrue();
public static Filter and(Filter... filters)
{
Filter res = ALWAYS_TRUE;
for(Filter f : filters) res = res.and(f);
return res;
}
}
Filter f = and(first, second, third);
|
Эта серия выглядит парадоксально – размер кода увеличивается от плохого кода к хорошему. Однако существенно лишь то, что клиентский код становится все более читаемым – все «лишнее» переносится в библиотечный код, раздуваясь в размерах, но становясь и более общим.
От клиентского кода требуется мгновенная читаемость, от библиотечного – читаемость при необходимости.
Еще один пример:
Плохо:
enum StringComparisonKind {EXACT, REGEX, GLOB}
enum StringPosition {ANYWHERE, WHOLE_STRING, STARTS_WITH, ENDS_WITH}
public class StringCondition {
...
public StringCondition(
String pattern, StringComparisonKind comparisonKind,
StringPosition position) {...}
}
conditions.add(new StringCondition(
"foo", StringComparisonKind.REGEX, StringPosition.ANYWHERE))
|
Хорошо:
import static StringComparisonKind.*;
import static StringPosition.*;
public class StringConditions {
public static regexWhole(String regex) {
return new StringCondition(regex, REGEX, WHOLE_STRING);
}
public static regexAnywhere(String regex) {
return new StringCondition(regex, REGEX, ANYWHERE);
}
public static exactWhole(String pattern) {
return new StringCondition(pattern, EXACT, WHOLE_STRING);
}
...
}
import static StringConditions.*;
conditions.add(regexAnywhere("foo"));
|
Абстракция, абстракция и еще раз абстракция. Удивительно, насколько ее обычно недооценивают.
Увеличиваем читаемость анонимных классов
Анонимные классы в Java – бледная замена замыканиям и анонимным функциям из функциональных языков, но при этом они, по крайней мере, обладают такой же мощностью и полезностью. Поэтому особенно остра необходимость увеличить их читаемость. Для этого стоит выносить их в константы или хотя бы локальные переменные:
Плохо:
List<Order> orders = CollectionUtils.flatten(CollectionUtils.map(
customers, new Function<Customer, List<Order>>()
{
public List<Order> apply(Customer customer)
{
return customer.getOrders();
}
}));
|
Хорошо:
import static CollectionUtils.*;
private static final Function<Customer, List<Order>> GET_ORDERS =
new Function<Customer, List<Order>>()
{
public List<Order> apply(Customer customer)
{
return customer.getOrders();
}
};
List<Order> orders = flatten(map(customers, GET_ORDERS));
|
(к сожалению, судя по всему, не существует способа избавиться от дублирования аргументов generic-ов).
По сути, это еще один пример переноса сложности и нечитаемости в библиотечный код – однако различие библиотечного и клиентского кода в данном случае более зыбкое.
Увеличиваем читаемость имен классов
Плохо:
CustomerProcessor taxes = new ComputeTaxesCustomerProcessor();
|
Эти суффиксы не дают вообще ничего. Если бы у Java не было пакетов (package) или статической типизации, то это было бы оправдано, чтобы не засорять глобальный namespace или случайно не перепутать один And с другим. Но они есть, и суффиксы не нужны – так же, как, например, венгерская нотация.
Хорошо:
CustomerProcessor taxes = new ComputeTaxes();
|
Увеличиваем читаемость generic-ов – typedef для бедных
Плохо:
class FooEverythingDoer
{
...
Map<String, String> getProperties(Foo foo) {...}
void putProperties(Foo foo, Map<String, String> properties) {...}
Map<Foo, Map<String, String>> getPropertiesBatch(Iterable<Foo> foos) {...}
Foo findByProperties(Map<String, String> partOfProperties) {...}
...
}
|
Хорошо:
class Properties extends Map<String,String>
{
(Конструкторы с сигнатурами базового класса)
}
class FooEverythingDoer
{
...
Properties getProperties(Foo foo) {...}
void putProperties(Foo foo, Properties properties) {...}
Map<Foo, Properties> getPropertiesBatch(Iterable<Foo> foos) {...}
Foo findByProperties(Properties partOfProperties) {...}
...
}
|
Плохо:
class MagicBarMerger
{
public void mergeIntoDb(List<MagicBar> bars)
{
List<MagicBar> existingBars = barDao.getAllBars();
Map<Integer, List<Pair<MagicBar, MagicBar>>> pairsById =
joinOnId(bars, existingBars);
List<MagicBar> merged = new ArrayList<MagicBar>();
for(Pair<MagicBar, MagicBar> pair : pairsById)
{
merged.add(merge(pair));
}
barDao.removeAllBars():
barDao.putBarsBatch(merged);
}
private Map<Integer, Pair<MagicBar, MagicBar>> joinOnId(
List<MagicBar> as, List<MagicBar> bs)
{
....
}
private MagicBar merge(Pair<MagicBar, MagicBar> bars)
{
....
}
}
|
Хорошо:
class MagicBarMerger
{
private static class Bars extends Pair<MagicBar,MagicBar>
{
(Конструктор с сигнатурой базового класса)
}
public void mergeIntoDb(List<MagicBar> bars)
{
List<MagicBar> existingBars = barDao.getAllBars();
Map<Integer, Bars> pairsById = joinOnId(bars, existingBars);
List<MagicBar> merged = new ArrayList<MagicBar>();
for(Bars bars : pairsById) {
merged.add(merge(bars));
}
barDao.removeAllBars():
barDao.putBarsBatch(merged);
}
private Map<Integer, Bars> joinOnId(List<MagicBar> as, List<MagicBar> bs)
{
....
}
private MagicBar merge(Bars bars)
{
....
}
}
|
Этот прием отчасти решает и проблему читаемости анонимных классов с типовыми параметрами:
Плохо:
import static CollectionUtils.*;
private static final Function<Customer, List<Order>> GET_ORDERS =
new Function<Customer, List<Order>>()
{
public List<Order> apply(Customer customer)
{
return customer.getOrders();
}
};
|
Хорошо:
interface CustomerFun<T> extends Function<Customer,T> {}
interface CustomerListFun<T> extends CustomerFun<List<T>> {}
import static CollectionUtils.*;
private static final CustomerListFun<Order> GET_ORDERS =
new CustomerListFun<Order>()
{
public List<Order> apply(Customer customer)
{
return customer.getOrders();
}
};
|
Source: http://rsdn.ru/article/java/JavaFP.xml |