menu

Повышение читаемости кода и функциональное программирование на Java Часть 1

Увеличиваем читаемость данных

Совсем плохо:

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
Category: Java | Added by: tsvetkov (06.03.2009)
Views: 1225 | Rating: 0.0/0