650 likes | 663 Views
Learn about the properties of code units, including how they should be understandable, loosely coupled, highly cohesive, composable, and protected from errors.
E N D
A Program Unit Should Be… • Understandable: Understanding a unit as-is, without understanding others. • Loosely Coupled: Minimal dependency on other units. • Highly Cohesive: Focused around a single task. • Composable: Can be used as a part of several (bigger) units. • Protected: Limited effect of errors
Low Cohesion • // class DatePanelpublic void processKey(KeyEvent e) { • int key = e.getKeyCode(); • if ((key == KeyEvent.VK_LEFT) || (key == 226)) • update(Calendar.DAY_OF_YEAR, -1); • else if ((key == KeyEvent.VK_RIGHT) || (key == 227)) • update(Calendar.DAY_OF_YEAR, 1); • else if ((key == KeyEvent.VK_PAGE_UP)) • update(Calendar.MONTH, -1); • ... • else • return; • if ((!e.isControlDown()) && (!e.isShiftDown())) { • model.clearSelection(); • if (e.isShiftDown()) • model.addSelection(model.getDate()); • ...
High Cohesion • // class DatePanel public void processKey(KeyEvent e) { • boolean changed = updateCalendar(e.getKeyCode()); • if (changed) • updateModel(e); • } • private boolean updateCalendar(int keycode) { • if ((key == KeyEvent.VK_LEFT) || (key == 226)) • update(Calendar.DAY_OF_YEAR, -1); • else if ((key == ...) • update(...) • else • return false; • return true; • } • private void updateModel(KeyEvent e) { • if ((!e.isControlDown()) ...
Coupling w/ ResultSet • public class C { • private List<String> names = new ArrayList<String>(); • public void collectNames(ResultSet rs, int n) { • for(int i = 0; i < n; ++i) { • if(!rs.next()) • return; • names.add(rs.getString(0)); • } • } • ... • }
Coupling w/ Iterator • public class C { • private List<String> names = new ArrayList<String>(); • public void collectNames(Iterator<String> iter, int n) { • for(int i = 0; i < n; ++i) { • if(!iter.hasNext()) • return; • names.add(iter.next()); • } • } • }
Even Less Coupling • public interface Processor { • public void process(String s); • } • public class C { • public void setProcessor(Processor arg) { p = arg; } • private Processor p; • public void collectNames(Iterator<String> iter, int n) { • for(int i = 0; i < n; ++i) { • if(!iter.hasNext()) • return; • p.process(iter.next()); • } • } • } • // => More Versatility, but less easy to use
public class Domino { • int left; • int right; • private Domino(left_, int right_) { • left = left; • right = right; • } • void flip() { • int temp = left; • left = right; • right = temp; • } • @Override • public String toString() { • return left + "-" + right; • } • }
void swap(Domino[] arr, int i, int j) { • Domino temp = arr[i]; • arr[i] = arr[j]; • arr[j] = temp; • }
boolean match(Domino a, Domino b) { • return a.right == b.left; • } • boolean solve(Domino[] arr, int pos) { • if (pos == arr.length) • return true; • for (int i = pos; i < arr.length; ++i) { • swap(arr, pos, i); • if((pos == 0 || match(arr[pos-1], arr[pos])) • && solve(arr, pos+1)) • return true; • arr[pos].flip(); • if((pos == 0 || match(arr[pos-1], arr[pos])) • && solve(arr, pos+1)) • return true; • swap(arr, pos, i); • } • return false; • }
boolean match(Domino[] arr, int pos) { • return pos == 0 || arr[pos - 1].right == arr[pos].left; • } • private boolean canSolve(Domino[] arr, int pos) { • if (pos == arr.length) • return true; • for (int i = pos; i < arr.length; ++i) { • swap(arr, pos, i); • if(match(arr, pos) && solve(arr, pos + 1)) • return true; • arr[pos].flip(); • if(match(arr, pos) && solve(arr, pos + 1)) • return true; • swap(arr, pos, i); • } • return false; • }
The Two Versions of match() • // General (i.e.: versatile) • boolean match(Domino a, Domino b) { • return a.right == b.left; • } • // More specific, but easier to use • // from the body of canSolve() • boolean match(Domino[] arr, int pos) { • return pos == 0 || arr[pos - 1].right == arr[pos].left; • }
Tradeoff • Ease of use vs. Versatility • C.collectNames() • match()
Duplication • Principles of Spartan programming: • Eliminate duplications • Minimization of: #lines, #columns, #tokens, #characters, #variables, #branches • DRY = Don’t Repeat Yourself • AKA: Deja-vue principle, Once and only once • Top principle in almost all approaches
DRY • Every piece of system knowledge should have one authoritative, unambiguous representation • Source code • Test plans • Database schemas • Documentation • etc.
Duplication in DatePanel • public void keyPressed(KeyEvent e) { • ... • if ((keycode == KeyEvent.VK_LEFT) || (keycode == 226)) { • int month = navigation.get(Calendar.MONTH); • navigation.add(Calendar.DAY_OF_YEAR, -1); • if (month != navigation.get(Calendar.MONTH)) { • fireMonthChangeListenerMonthDecreased(new MonthChangeEvent( • this, navigation.getTime())); • setDate(navigation.getTime()); • } • changed = true; • } • if ((keycode == KeyEvent.VK_RIGHT) || (keycode == 227)) { • int month = navigation.get(Calendar.MONTH); • navigation.add(Calendar.DAY_OF_YEAR, 1); • if (month != navigation.get(Calendar.MONTH)) { • fireMonthChangeListenerMonthIncreased(new MonthChangeEvent( • this, navigation.getTime())); • setDate(navigation.getTime()); • } • changed = true; • } • ...
Side Effects • Duplicated bug => Multiple fixes • Need to change => Multiple changes • A lot of code: Hard to understand • Multiple coupling (instead of one) • Indiscriminate copying => Bug • (See lines 146, 154 in dry-datepanel.java)
Code-level Duplication • Computations: Algorithms, logic • Expressions • Literals • Data • Data format • Types • Hierarchies • … • (Many simple cases in the Spartan examples. • Let’s complicate things…)
// Class: Dispatcher • public Object eval(PrintWriter out, String... args) { • for(Controller c : controllers) { • if(c.understands(args)) • return c.evaluate(out, args); • } • throw new RuntimeException("Unknown command"); • }
// class: Controller • private String typeName; • private Database db; • public boolean understands(String... args) { • String s = "add-" + typeName; • if(args.length >= 2 && s.equals(args[0])) • return true; • s = "list-" + typeName + "s"; • if(s.equals(args[0])) • return true; • s = "show-" + typeName; • if((args.length >= 2) && s.equals(args[0])) • return true; • return false; • }
// class: Controller • public Object evaluate(PrintWriter out, String... args) { • String s = "add-" + typeName; • if(args.length >= 2 && s.equals(args[0])) • return create(out, args); • s = "list-" + typeName + "s"; • if(s.equals(args[0])) • return list(out, args); • s = "show-" + typeName; • if((args.length >= 2) && s.equals(args[0])) • return show(out, args); • DBC.impossible(); • return null; // Faked • }
// Class: Controller • public Object create(PrintWriter out, String... args) { • String id = db.newRecord(typeName); • db.put(id, "name", args[1]); • for(int i = 3; i < args.length; i += 2) { • String key = args[i-1]; • String value = args[i]; • db.put(id, key, value); • } • return id; • }
public class Program { • private static final int LOGIN = 0; • private static final int UPLOAD = 1; • private static final int DOWNLOAD = 2; • private static final REQ_KIND_INDEX = 10; • private Font font; • public Program() { • Map<String, String> properties = ...; • font = createFont(properties); • ... • } • public Font createFont(Map<String, String> properties) { • String name = "Arial"; • if (properties.get("font-name") != null) • name = properties.get("font-name"); • int size = 10; • if (properties.get("font-size") != null) • size = Integer.parseInt(properties.get("font-size")); • return new Font(name, Font.PLAIN, size); • }
public void prepareLoginRequest(byte[] bytes) { • bytes[REQ_KIND_INDEX] = LOGIN; • ... • } • public void prepareUploadRequest(byte[] bytes) { • bytes[REQ_KIND_INDEX] = UPLOAD; • ... • } • public void prepareDownloadRequest(byte[] bytes) { • bytes[REQ_KIND_INDEX] = DOWNLOAD; • ... • } • ... • // the 10’s are duplicated, yet DRY is not violated!
public class Stringer { • StringBuilder sb = new StringBuilder(); • public void add(Object o) { • if (sb.length() > 0) • sb.append(", "); • sb.append(format(o)); • } • public String format(Object o) { • return o.toString(); • } • @Override public String toString() { • return sb.toString(); • } • }
public class OutputGenerator { • private void print(PrintWriter out, Stringer stringer, • Object[] objects) • { • for(Object o : objects) { • if(o != null) • stringer.add(o); • } • out.println(stringer.toString()); • } • public void generate() { • PrintWriter out = ...; • Date[] dates = ...; • print(out, new Stringer(), dates); • ... • String[] names = ...; • print(out, new Stringer(), names); • } • }
// org.objectweb.asm.ClassWriter • public byte[] toByteArray() { • int size = 24 + 2 * interfaceCount; • ... • int attributeCount = 0; • if (ClassReader.SIGNATURES && signature != 0) { • ++attributeCount; • size += 8; • newUTF8("Signature"); • } • if (sourceFile != 0) { • ++attributeCount; • size += 8; • newUTF8("SourceFile"); • } • if (sourceDebug != null) { • ++attributeCount; • size += sourceDebug.length + 4; • newUTF8("SourceDebugExtension"); • } • // 45 lines of similarly structured code...
private class AttributeWriter { • int attributeCount = 0; • int size; • public AttributeWriter(int size_) { • size = size_; • } • public void put(boolean b, int sizeInc, String s) { • if (!b) • return; • ++attributeCount; • size += sizeInc; • newUTF8(s); • } • }
AttributeWriter aw = new AttributeWriter(size); • aw.put(ClassReader.SIGNATURES && signature != 0, 8, • "Signature"); • aw.put(sourceFile != 0, 8, "SourceFile"); • aw.put(sourceDebug != null, sourceDebug.length + 4, • "SourceDebugExtension"); • ... • attributeCount = aw.attributeCount; • size = aw.size; • // Are we DRY-compliant now?
private static class Node { • private Node left; • private Node right; • private int value; • public Node(int value_) { value = value_; } • public static int height(Node n) { • return (n == null) ? 0 : • 1 + Math.max(height(n.left), height(n.right)); • } • public static Node insert(Node parent, Node child) { • if(parent == null) • return child; • if (child.value < parent.value) • parent.left = insert(parent.left, child); • else • parent.right = insert(parent.right, child); • return parent; • }
public static void print(Node n, int depth) { • if(n == null) • return; • print(n.left, depth + 1); • for (int i = 0; i < depth; ++i) • System.out.print(" "); • System.out.println(n.value); • print(n.right, depth + 1); • } • public static void inorder(Node n, List<Integer> results) { • if(n == null) • return; • inorder(n.left, results); • results.add(n.value); • inorder(n.right, results); • } • }
Binary Search Tree (impl. #1) • Leaf node represented as null • Cons: • Static methods (no overriding) • null checks are all over the place => DRY violated
interface Node { • public int value(); • public Node left(); • public Node right(); • public int height(); • public Node insert(Node child); • public void inorder(List<Integer> results); • public void print(int depth); • }