Untitled

Werbung
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
Herunterladen