Clojure Clojure • Entwicklungsdauer: ca. 3 Jahre • Release: 2007 • Autor: Rich Hickey • Clojure ist ein praktisches Lisp für die JVM • Clojure ist geeignet für grosse Systeme (z.B. Datomic) Designziele • Simplicity! • Integration mit Java (Lisp Island Problem) • Geschrieben um Java zu ersetzen • Funktionale Sprache Features • Direkte Integration mit der JVM (vs. Jython/JRuby) • Persistent Data Structures (Immutability as default) • Concurrency Support (Time Model) • (erweiterte) Lisp Syntax “The most significant technical advantage the Clojure community has [...] is that you guys are bringing a gun to a knife fight. Because you have Lisp.” –Neal Ford Ist Clojure dynamisch? • REPL? Ja! • Dynamisch typisiert? Ja! • Alles ist ein Objekt? Ja! (1) • GC? Ja! • Interpretiert? Quatsch, es soll ja schnell sein! (2) • Reflection? Ja! • Dynamic Scoping? Ja! (3) (1) Man hat aber auch Zugriff auf Primitives (2) Es gibt Kompilation zur Laufzeit (3) Ist aber eine ziemlich dumme Idee Leadership in Software Design Warnung! • Bitte die Ironie- und Sarkasmus-Sensoren einschalten • Das ist alles nur geklaut: • Stuart Halloway • Rich Hickey • Das hier ist eine Werbeveranstaltung für die Vorlesung “Funktionale Programmierung” Die Mission • Die Softwarewelt ist in einem ständigen, rapiden Wandel • Der uns gefährlich werden kann, wenn wir nicht mithalten • Es gibt geheime Tricks, mit denen wir uns vor Wandel schützen können • Unser Code muss so kompliziert werden, dann nur wir ihn verstehen • Und somit unersetzlich werden Das Problem • Wir dürfen nicht zu plump vorgehen • Das wird schnell bemerkt und wir sind den Job los Python f=lambda x=['31312405171810171211313126112','718974937474 7628707','9626701','8873809195908988937382747529 513','807979748182306979652922711','251414211226 26106',' hi there!'],z=globals(),y=lambda x: reduce(lambda x,y:x+y,map(lambda x,y=x,z=lambda x:int(x[:2]):chr((z(y[x*int(y[-5]):])^z(y[-4:])) +z(y[-4:])),range(int(x[-2:] )))):map(lambda z,x=y,y=x,w=z:z(z(w[x(y[2])],x(y[3])) [0],x(y[5]))(x(y[4])),[vars(z[y(x[0])]) [y(x[1])]]) and x[-1];print f() Hinweis: Alles in eine Zeile schreiben Python Z U O E FF f=lambda x=['31312405171810171211313126112','718974937474 7628707','9626701','8873809195908988937382747529 513','807979748182306979652922711','251414211226 26106',' hi there!'],z=globals(),y=lambda x: reduce(lambda x,y:x+y,map(lambda x,y=x,z=lambda x:int(x[:2]):chr((z(y[x*int(y[-5]):])^z(y[-4:])) +z(y[-4:])),range(int(x[-2:] )))):map(lambda z,x=y,y=x,w=z:z(z(w[x(y[2])],x(y[3])) [0],x(y[5]))(x(y[4])),[vars(z[y(x[0])]) [y(x[1])]]) and x[-1];print f() S N T H IC LI ! H C Hinweis: Alles in eine Zeile schreiben PHP <?! echo("<p>Search results for query: " .! $_GET['query'] . ".</p>");! ?> PHP Z U E FF O <?! echo("<p>Search results for query: " .! $_GET['query'] . ".</p>");! ?> S N T H IC LI ! H C Java try {! foo(a, b);! } catch (IllegalArgumentException e) {! new CaseLibException(e);! } catch (IOException e) {! new CaseLibException(e);! } Java Z U O E FF try {! foo(a, b);! } catch (IllegalArgumentException e) {! new CaseLibException(e);! } catch (IOException e) {! new CaseLibException(e);! } S N T H IC LI ! H C Unwartbarkeit 101 • Der Plan • Wir schreiben Lehrbuch und best-practice kompatiblen Code • Wir bekommen trotzdem unwartbare Software Danke, Objektorientierung ! Meine Position im System Mein Modul Das Ziel • Mein Modul soll zum Zentrum des Systems werden • Ich will meine Bedingungen dem System aufzwingen • Die Taktik die hilft: Verflechtung (complecting) Maßnahme 1 Invarianten aufweichen Maßnahme 1 Konstruktor new Foo(…) ? Kein Zugriff auf die Instanz Wie können wir das korrekte Objekt kaputt bekommen? (hoffentlich) korrektes Objekt Maßnahme 1 • Verwende wann immer es geht setter Methoden • Setter Methoden können Objekte nach der Konstruktion inkonsistent machen • Ziel ist nicht absichtlich Objekte zu zerstören • Ziel ist es unser Objekt mit anderen Objekten zu verflechten • Insbesondere wollen wir es Anderen einfach machen sich mit unserem Objekt zu verflechen und so in unsere Falle zu tappen Beispiel ! /*Java Bean: * 1) public default constructor * 2) Serializable * 3) Getter / Setter for all properties * */ public class Address implements Serializable { ! private private private private public Address() { } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public int getPostalCode() { return postalCode; } public void setPostalCode(int postalCode) { this.postalCode = postalCode; } ! ! ! ! ! public String getCity() { return city; } public void setCity(String city) { this.city = city; } ! static final long serialVersionUID = 2323317537288737219L; String street; int postalCode; String city; ! } Valide Java Bean! Das ist der Industrie Standard!!! ! Die Klasse ist eine Bean, die zwei Strings und einen Integer Wert “kapselt” ! Ok Ok, ich wechsele zu Groovy Beispiel public class Address { String street; Integer postalCode; String city; ! def show() { println("${this.getStreet()}, ${this.getPostal()} ${this.getCity()}") } } ! class Book { Map book; def show() { book.each{ k,v -> print "${k}: "; v.show()} } } ! def a = new Address(street:"Universitaetsstr. 1", postal:40225, city:"Duesseldorf") a.show() def b = new Book() b.setBook(["Jens":a]) b.show() Beispiel public class Address { String street; Integer postalCode; String city; ! def show() { println("${this.getStreet()}, ${this.getPostal()} ${this.getCity()}") } } ! class Book { Map book; def show() { book.each{ k,v -> print "${k}: "; v.show()} } } ! def a = new Address(street:"Universitaetsstr. 1", postal:40225, city:"Duesseldorf") a.show() def b = new Book() b.setBook(["Jens":a]) b.show() b.setBook(null) b.show() Defensives Programmieren • Verteidigungsstrategie gegen Setter? • Eine validate Methode, die vom Setter aufgerufen wird class SafeBook { Map book; def setBook(b) { assert b != null; book = b } } b.setBook(null); Assertion failed: ! assert b != null | | | false null Bug! • Die Map ist ja auch veränderlich def x = b.getBook() x[x.keySet().first()] = null b.setBook(x); b.show() java.lang.NullPointerException def setBook(b) { assert b != null; assert (b.findAll {k,v -> v == null}).isEmpty() book = b } Defensives Programmieren • Jetzt haben wir ein Performance Problem • Wenn die Collection gross ist müssen wie bei jedem Setter Aufruf jedes Element prüfen • Das geht auch rekursiv weiter! Defensives Programmieren • Als netten Seiteneffekt verunreinigen wir mit der Validation den Aufrufer des Setters • Der muss mit Fehlern umgehen Setter • Setter sind ein phantastisches Konstrukt • Sie sorgen dafür, dass Niemand, der unsere Klasse benutzt sicher sein kann, dass seine Klasse konsistent bleibt Offensichtlich falsch? • Nein! Setter und Java Beans sind in der Praxis völlig normal • Stichwort: Enterprise Java Beans Mutability • Mutability ist grundsätzlich gerechtfertigt • RAM ist teuer • Persistenter Speicher ist teuer und langsam • Computer sind wertvoll und selten • Ressourcen werden niemals gemeinsam genutzt Mutable Structures • Update in place macht einige Dinge schön schwierig • sharing / distributing • Nebenläufiger Zugriff • Caching Verwendung • Immer und überall! • Das ist vollkommen im OO Land akzeptiert • Falls Du gezwungen wirst immutable Typen zu benutzen kannst Du das durch eine naive Implementierung (z.B. CopyOnWriteArray) unterwandern Maßnahme 2 API >> Daten Beispiel: JUnit <?xml version="1.0" encoding="UTF-8"?>! <testsuite name="de.be4.classicalb.core.parser.PredicatesTest" tests="18" skipped="0" failures="0" errors="0" timestamp="2014-05-22T23:02:54" hostname="Zoidberg-2.local" time="0.02">! <properties/>! <testcase name="testAndOrPrio" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.002"/>! <testcase name="testParallelMember" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testParallelBelongs2" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testInvariant1" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testForall" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testForallCouple1" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testCoupleInExists1" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testCoupleInForall1" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.0"/>! <testcase name="testExampleThesis1" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testMultiCompositions" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.0"/>! <testcase name="testWithComposition" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testEqualVsImplication" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/ >! <testcase name="testEqualVsImplicationFormula" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testNonIdentifiersInQuantification" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testNonIdentifiersInQuantificationFormula" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testSubstitutionInPredicate" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.002"/>! <testcase name="testNoPredicateSubstitutionsInNormalMode" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <testcase name="testBFalse" classname="de.be4.classicalb.core.parser.PredicatesTest" time="0.001"/>! <system-out><![CDATA[]]></system-out>! <system-err><![CDATA[]]></system-err>! </testsuite>! Der Feind: Daten • Der Report entkoppelt Komponenten • Muss JUnit laufen um den Report zu lesen? • Welche Sprache muss ich benutzen um den Report zu lesen? Dein Freund: Die API • Was wäre, wenn JUnit den Report per API calls liefen würde? • JUnit müsste laufen um das Ergebnis zu lesen (zeitliche Kopplung) • Und man müsste mit JUnit sprechen können (also auf der JVM laufen) • Für Bonuspunkte: Es würde sich auch noch die Möglichkeit ergeben das Ganze mutable zu machen Beispiel 2: JUnit Beispiel 2: JUnit Fazit • JUnit könnte verbessert werden indem man das XML File los wird • Stattdessen könnte man eine Callback API einführen Maßnahme 3 DSL >> API >> Daten “Nothing says ‘Skrew you!’ like a DSL.” @stuarthalloway DSL DSL API Informations Model schlecht DSL DSL API Info besser DSL DSL ausgezeichnet Brilliantes Beispiel: SQL! Beispiel: SQL Gegenmassnahmen • Problem : Du wirst gezwungen wird ein Informationsmodel zur Verfügung zu stellen • Lösung: Mach das Informationsmodel mutable! Maßnahme 4 Mehr Abstraktion Mal unter uns • Es gibt nur wenige Formen in denen Information vorkommt • Skalare (Zahlen, Zeichen, …) • Sequenzen (Listen, Vektoren, …) • Mengen • Assoziativer Speicher (Map) Niemals rausgeben • Informationen dürfen niemals in Ihrer Grundform nach aussen gegeben werden • Dann könnten Nutzer einfach Ihre Bibliotheken für Collections benutzen Encapsulate! • Encapsulation ist eine Wunderwaffe um Komplexität zu erzeugen • Man sollte immer eine Klasse spezifizieren! Damit werden alle generischen Funktionen nutzlos • Optimal ist natürlich Beans zu benutzen! • Niemand wird Verdacht schöpfen, denn das ist absolut gängige OO Praxis Statische Typisierung • Statische Typisierung ist nicht generell gut für uns • Aber wenn wir sie über Grenzen von Subsystemen hinweg verwenden ist sie eine Komplexitätsquelle erster Klasse • Und niemand wird sich beschweren! Wir spezifizieren ja nur exakt was für Daten wir versenden Ich möchte ein Spiel spielen Produzent Konsument Wieviele Zwischensysteme kannst du brechen? Exceptions! • Noch besser als mit Daten funktioniert es mit Fehlern • Benutzt checked Exceptions • Am Besten für jedes Problem eine eigene Exceptionklasse • Egal, ob Abbruch das Einzige ist, was das System machen kann • Und schreibt unbedingt auch einen Unit-Test, der die Exception testet Apropos Tests Maßnahme 5 Tests Vorsicht • Nicht jede Form von Testing ist gut um Komplexität zu erreichen! • Wir können aber ganz prima jede Menge Komplexität in den üblichen Unit Tests unterbringen • • Wir können überspezifizieren • Wir können eine super tolle Test DSL implementieren Das ist Industriestandard! Unit Testing • Und weil wir agile sind, sind Unit Tests unser Allheilmittel • Tests sind unsere Dokumentation • Wir brauchen auch keine Code Reviews, wozu haben wir Tests? Brilliantes Beispiel @Test public void testEmptyMachine() throws Exception { final BParser parser = new BParser("testcase"); final String testMachine = "MACHINE SimplyStructure END"; final Start startNode = parser.parse(testMachine, true); final AAbstractMachineParseUnit machine = (AAbstractMachineParseUnit) startNode .getPParseUnit(); final AMachineHeader header = (AMachineHeader) machine.getHeader(); assertEquals("Machine name not as expected", "SimplyStructure", header.getName().get(0).getText()); assertNotNull("Machine header parameter list is null",header.getParameters()); assertTrue("More machine header parameters than expected", header.getParameters().size() == 0); } final LinkedList<PMachineClause> machineClauses = machine.getMachineClauses(); assertNotNull("Machine clause list is null", machineClauses); assertTrue("More machine clauses than expected",machineClauses.size() == 0); ! ! ! ! ! Um die Identität des Programmierers zu schützen nennen wir ihn einfach J. Bendisposto. Oder nein… das ist zu offensichtlich. Sagen wir lieber Jens B. Brilliantes Beispiel @Test public void testEmptyMachine() throws Exception { final BParser parser = new BParser("testcase"); final String testMachine = "MACHINE SimplyStructure END"; final Start startNode = parser.parse(testMachine, true); final AAbstractMachineParseUnit machine = (AAbstractMachineParseUnit) startNode .getPParseUnit(); final AMachineHeader header = (AMachineHeader) machine.getHeader(); assertEquals("Machine name not as expected", "SimplyStructure", header.getName().get(0).getText()); assertNotNull("Machine header parameter list is null",header.getParameters()); assertTrue("More machine header parameters than expected", header.getParameters().size() == 0); } final LinkedList<PMachineClause> machineClauses = machine.getMachineClauses(); assertNotNull("Machine clause list is null", machineClauses); assertTrue("More machine clauses than expected",machineClauses.size() == 0); ! ! ! ! ! Setup Brilliantes Beispiel @Test public void testEmptyMachine() throws Exception { final BParser parser = new BParser("testcase"); final String testMachine = "MACHINE SimplyStructure END"; final Start startNode = parser.parse(testMachine, true); final AAbstractMachineParseUnit machine = (AAbstractMachineParseUnit) startNode .getPParseUnit(); final AMachineHeader header = (AMachineHeader) machine.getHeader(); assertEquals("Machine name not as expected", "SimplyStructure", header.getName().get(0).getText()); assertNotNull("Machine header parameter list is null",header.getParameters()); assertTrue("More machine header parameters than expected", header.getParameters().size() == 0); } final LinkedList<PMachineClause> machineClauses = machine.getMachineClauses(); assertNotNull("Machine clause list is null", machineClauses); assertTrue("More machine clauses than expected",machineClauses.size() == 0); ! ! ! ! ! Eingabe Brilliantes Beispiel @Test public void testEmptyMachine() throws Exception { final BParser parser = new BParser("testcase"); final String testMachine = "MACHINE SimplyStructure END"; final Start startNode = parser.parse(testMachine, true); final AAbstractMachineParseUnit machine = (AAbstractMachineParseUnit) startNode .getPParseUnit(); final AMachineHeader header = (AMachineHeader) machine.getHeader(); assertEquals("Machine name not as expected", "SimplyStructure", header.getName().get(0).getText()); assertNotNull("Machine header parameter list is null",header.getParameters()); assertTrue("More machine header parameters than expected", header.getParameters().size() == 0); } final LinkedList<PMachineClause> machineClauses = machine.getMachineClauses(); assertNotNull("Machine clause list is null", machineClauses); assertTrue("More machine clauses than expected",machineClauses.size() == 0); ! ! ! ! ! Ausführung Brilliantes Beispiel @Test public void testEmptyMachine() throws Exception { final BParser parser = new BParser("testcase"); final String testMachine = "MACHINE SimplyStructure END"; final Start startNode = parser.parse(testMachine, true); final AAbstractMachineParseUnit machine = (AAbstractMachineParseUnit) startNode .getPParseUnit(); final AMachineHeader header = (AMachineHeader) machine.getHeader(); assertEquals("Machine name not as expected", "SimplyStructure", header.getName().get(0).getText()); assertNotNull("Machine header parameter list is null",header.getParameters()); assertTrue("More machine header parameters than expected", header.getParameters().size() == 0); } final LinkedList<PMachineClause> machineClauses = machine.getMachineClauses(); assertNotNull("Machine clause list is null", machineClauses); assertTrue("More machine clauses than expected",machineClauses.size() == 0); ! ! ! ! ! Ausgabe Brilliantes Beispiel @Test public void testEmptyMachine() throws Exception { final BParser parser = new BParser("testcase"); final String testMachine = "MACHINE SimplyStructure END"; final Start startNode = parser.parse(testMachine, true); final AAbstractMachineParseUnit machine = (AAbstractMachineParseUnit) startNode .getPParseUnit(); final AMachineHeader header = (AMachineHeader) machine.getHeader(); assertEquals("Machine name not as expected", "SimplyStructure", header.getName().get(0).getText()); assertNotNull("Machine header parameter list is null",header.getParameters()); assertTrue("More machine header parameters than expected", header.getParameters().size() == 0); } final LinkedList<PMachineClause> machineClauses = machine.getMachineClauses(); assertNotNull("Machine clause list is null", machineClauses); assertTrue("More machine clauses than expected",machineClauses.size() == 0); ! ! ! ! ! Validierung Die böse Art von Tests • Es gibt generatives Testing. Das ist die böse Sorte Testing • Man erzeugt ein Model von dem Aussehen seiner Daten • Man generiert Input • Führt den Code mit dem Input aus • Persistiert Input und zugehörigen Output • Validation durch Properties Testmanufaktur • Tests sollen in Handarbeit erstellt werden • Wir wollen uns Test-Eingaben und Ausgaben ausdenken • Einen Test schreiben, der prüft, ob die Ausgabe zur erwarteten Resultat passt • Unter uns: Wenn der Output komplex ist kopieren wir eh den Output in das assert “Your mock object is a joke; that object is mocking you. For needing it.” –Rich Hickey Maßnahme 6 Queuing vermeiden Queues • Queues entkopplen Komponenten voneinander • Meidet daher unbedingt Queues • Ist sogar ziemlich leicht, da es in OO sowieso selten gemacht wird • Regel 1: Nicht über Queues sprechen • Regel 2: Nicht über Queues sprechen Der Feind: Queues Objektorientierung • Objekte immer direkt verknüpfen (durch Instanzen) • Gut: Dependency Injection • Besser: Erzeugung vor Ort Maßnahme 7 Sprachsemantik zur Kommunikation (gut verträglich mit statischer Typisierung) Sprachsemantik • Ich verwende meine Sprachsemantik für Kommunikation • Jetzt ist es auch deine Semantik Erfolgsmodelle Bloss nicht Welche darf es sein? Welche darf es sein? protobuf yaml Java xml json edn csv Was ist wichtiger für die Schnittstelle zwischen Subsystemen? Unsere Wahl • Java natürlich • Wir erzwingen die Sprachsemantik auf der entfernten Seite • Jedes System auf dem Weg muss mitspielen (wegen der Typisierung) • Bedauerlicherweise ist JSON in letzter Zeit sehr populär geworden JSON {! "selector": "[id$=gear_cylinder_l]",! "bindings": [! {! "type": "predicate",! "formula": "",! "attr": "fill",! "value": "#cccccc"! },! {! "type": "predicate",! "formula": "gear = gear_moving & extend_gear_valve = valve_open",! "attr": "fill",! "value": "#88d2f7"! },! {! "type": "predicate",! "formula": "(gear = extended & extend_gear_valve = valve_open) ! or (gear = retracted & retract_gear_valve = valve_open)",! "attr": "fill",! "value": "#88d2f7"! }! ]! } JSON angreifen • JSON hat Schwächen in der Semantik: Zu wenige "Typen" • Der Trick ist APIs zu produzieren, die JSON herausgeben • Dadurch muss jeder selber seine Typkonvertierungen schreiben Fazit 1 • Der Nummer 1 Weg zur Glückseligkeit ist Mutability • Dicht gefolgt von Überabstraktion • Die aktuelle Praxis der objektorientierten Programmierung unterstützt uns bei beiden Dingen Fazit 2 • Kommt in meine Vorlesung Funktionale Programmierung • Dort werden wir uns darüber unterhalten, wie es anders geht