1 / 65

Properties of Code

Learn about the properties of code units, including how they should be understandable, loosely coupled, highly cohesive, composable, and protected from errors.

idavid
Download Presentation

Properties of Code

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Properties of Code

  2. 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

  3. <cohesion>

  4. 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()); • ...

  5. 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()) ...

  6. </cohesion>

  7. <coupling>

  8. 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)); • } • } • ... • }

  9. 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()); • } • } • }

  10. 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

  11. </coupling>

  12. <example.domino>

  13. 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; • } • }

  14. void swap(Domino[] arr, int i, int j) { • Domino temp = arr[i]; • arr[i] = arr[j]; • arr[j] = temp; • }

  15. 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; • }

  16. 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; • }

  17. 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; • }

  18. Tradeoff • Ease of use vs. Versatility • C.collectNames() • match()

  19. </example.domino>

  20. <dont.repeat.yourself>

  21. 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

  22. DRY • Every piece of system knowledge should have one authoritative, unambiguous representation • Source code • Test plans • Database schemas • Documentation • etc.

  23. 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; • } • ...

  24. 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)

  25. Code-level Duplication • Computations: Algorithms, logic • Expressions • Literals • Data • Data format • Types • Hierarchies • … • (Many simple cases in the Spartan examples. • Let’s complicate things…)

  26. <logic>

  27. // 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"); • }

  28. // 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; • }

  29. // 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 • }

  30. // 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; • }

  31. </logic>

  32. <literals>

  33. 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); • }

  34. 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!

  35. </literals>

  36. <new.expressions>

  37. 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(); • } • }

  38. 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); • } • }

  39. </new.expressions>

  40. <example.asm>

  41. // 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...

  42. 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); • } • }

  43. 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?

  44. </example.asm>

  45. </dont.repeat.yourself>

  46. <scattering>

  47. 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; • }

  48. 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); • } • }

  49. Binary Search Tree (impl. #1) • Leaf node represented as null • Cons: • Static methods (no overriding) • null checks are all over the place => DRY violated

  50. 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); • }

More Related