changeset 117:cac91c4dfae8

Mimic finally block in Java loop honoring continue statement.
author Oleksandr Gavenko <gavenkoa@gmail.com>
date Mon, 27 Mar 2017 00:41:16 +0300
parents f8d45fa94539
children 3220adaa9740
files 631f6b21-01b6-4d50-8e0f-13f59bd66fc2/index.rst
diffstat 1 files changed, 206 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/631f6b21-01b6-4d50-8e0f-13f59bd66fc2/index.rst	Mon Mar 27 00:41:16 2017 +0300
@@ -0,0 +1,206 @@
+
+==============================================================
+ Mimic finally block in Java loop honoring continue statement
+==============================================================
+:created: 2017-03-26 23:00
+:updated: 2017-03-26
+:tags: java, lang
+
+Many programmers put main execution flow inside several levels of "if" statements that guard against
+violation of processing pre-conditions.
+
+I think that this is a faulty practice.
+
+At first sight it looks like explicit checks for proper state are more meaningful then checks for
+malformed or undesired state.
+
+Putting main execution flow inside several ``if`` means that your main code will have several levels
+of indenting. Indenting is an indicator of more specialized, detailed code that at brief look should
+be ignored.
+
+But more important, the way of thinking during code review and troubleshooting lays in getting rid
+of undesired states that may lead to operation on unintended state.
+
+I think that code should detect problems and interrupt execution (or move to processing next portion
+of information).
+
+Using interruptions you guaranty that next code will operate with proper incoming state. And you
+don't need to guard checks, eliminating checks nesting and allowing sequencing checks without
+code indenting. All code will lay on the same level!
+
+Maintaining code with checks first and keeping all on single level can be tricky because language
+designers don't worried about such use case.
+
+Let's look to task for reporting that some adjusted elements in sequence have certain properties.
+
+That would be a report on flights with moved departure time. We have full history of all flights in
+itinerary::
+
+    public static class Flight {
+        public String from;
+        public String to;
+        public String datetime;
+        public Status status;
+        public Flight(String from, String to, String details, Status status) {
+            this.from = from;
+            this.to = to;
+            this.datetime = details;
+            this.status = status;
+        }
+    }
+
+    public enum Status {
+        AVAILABLE, CANCELED;
+    }
+
+    public static void main(String[] args) {
+        List<Flight> travel = Arrays.asList(
+                new Flight("TLV", "ABC", "12:30", Status.AVAILABLE),
+                new Flight("ABC", "XYZ", "11:20", Status.CANCELED),
+                new Flight("ABC", "XYZ", "11:15", Status.CANCELED),
+                new Flight("ABC", "XYZ", "12:20", Status.AVAILABLE),
+                new Flight("XYZ", "ABC", "06:30", Status.CANCELED),
+                new Flight("XYZ", "ABC", "20:15", Status.AVAILABLE),
+                new Flight("ABC", "TLV", "12:30", Status.AVAILABLE));
+  }
+
+Report will look like::
+
+  [flight: ABC=>XYZ: time: 11:15->12:20], [flight: XYZ=>ABC: time: 06:30->20:15]
+
+with helpers::
+
+    private static String formatNote(String fromAirport, String toAirport, String oldDateTime, String newDateTime) {
+        return String.format("[flight: %s=>%s: time: %s->%s]",
+                fromAirport, toAirport, oldDateTime, newDateTime);
+    }
+
+    private static void dumpNotes(List<String> notes) {
+        String msg = String.join(", ", notes);
+        System.out.println(msg);
+    }
+
+Naive attempt to follow introduced principle leads to code duplication::
+
+    private static List<String> duplicatedAssignments(List<Flight> travel) {
+        List<String> notes = new LinkedList<>();
+        Flight prev = null;
+        for (Flight curr : travel) {
+            if (prev == null) {
+                prev = curr;
+                continue;
+            }
+            if (curr.status != Status.AVAILABLE) {
+                prev = curr;
+                continue;
+            }
+            if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to)) {
+                prev = curr;
+                continue;
+            }
+
+            String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
+            notes.add(note);
+            prev = curr;
+        }
+        return notes;
+    }
+
+Most of developers rewrite above code into::
+
+    private static List<String> indentedBusinessLogic(List<Flight> travel) {
+        List<String> notes = new LinkedList<>();
+        Flight prev = null;
+        for (Flight curr : travel) {
+            if (prev != null
+                    && curr.status == Status.AVAILABLE
+                    && curr.from.equals(prev.from)
+                    && curr.to.equals(prev.to)) {
+                String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
+                notes.add(note);
+            }
+            prev = curr;
+        }
+        return notes;
+    }
+
+The problem with first piece of code is that Java ``for``-each loop doesn't allow execution of
+common block of code regardless of ``continue`` statement.
+
+But another form of ``for`` loop allows execution of block of code in case of ``continue``
+statement::
+
+    private static List<String> forLoopTrick(List<Flight> travel) {
+        List<String> notes = new LinkedList<>();
+        Iterator<Flight> iter = travel.iterator();
+        Flight curr, prev = null;
+
+        for (; iter.hasNext(); prev = curr) {
+            curr = iter.next();
+            if (prev == null)
+                continue;
+            if (curr.status != Status.AVAILABLE)
+                continue;
+            if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to))
+                continue;
+
+            String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
+            notes.add(note);
+        }
+        return notes;
+    }
+
+So Java control statements in some way primitive and lack rich loop statement with ``finally`` block
+that executed on loop and in case of ``continue``.
+
+It can be emulated in two ways. With moving from sequential flow via exception::
+
+    private static class NonLocalExit extends Exception {}
+
+    private static List<String> nonLocalExitTrick(List<Flight> travel) {
+        List<String> notes = new LinkedList<>();
+        Flight prev = null;
+        for (Flight curr : travel) {
+            try {
+                if (prev == null)
+                    throw new NonLocalExit();
+                if (curr.status != Status.AVAILABLE)
+                    throw new NonLocalExit();
+                if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to))
+                    throw new NonLocalExit();
+                String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
+                notes.add(note);
+            } catch (NonLocalExit ex) { }
+            prev = curr;
+        }
+        return notes;
+    }
+
+and with moving from sequential flow via fake "do-while" loop (which is more efficient)::
+
+    private static List<String> fakeLoopInside(List<Flight> travel) {
+        List<String> notes = new LinkedList<>();
+        Flight prev = null;
+        for (Flight curr : travel) {
+            do {
+                if (prev == null)
+                    break;
+                if (curr.status != Status.AVAILABLE)
+                    break;
+                if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to))
+                    break;
+                String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
+                notes.add(note);
+            } while (false);
+            prev = curr;
+        }
+        return notes;
+    }
+
+What you can see is that Java lacks ``goto``! xD
+
+You may think that workaround with using exception can be designed only by mentally ill developer.
+
+You should know that some languages (like Elisp) have no ``break`` or ``continue`` statement.
+Throwing / catching an exception is only the way to mimic ``break`` and ``continue``.
+