Skip to main content

· 3 min read

I have updated my Perceptron-implementation with a plotting function that allows for visualizing the adjustments of the Perceptron's weight-vector through the epochs.

The source-code can be found at https://github.com/ThorstenSuckow/pylabs.

Usage

Create input data and the associated output values. As an example, the following represents the logical AND-function:

import numpy as np
from Perceptron import Perceptron

# input
X = np.array([
[0, 0], [0, 1], [1, 0], [1, 1]
])

# output
y = np.array([0, 0, 0, 1])

In the next step, the Perceptron is created.

p = Perceptron(50, 0.3)

Once a Perceptron-instance is available, you can pass the input- and output-values to learn():

p.learn(X, y)

and test data with

result = p.test([0, 0])

result holds the computed weight vector if the training data could be separated within the epochs. If that failed, None is returned.

Note: The bias is available with p.bias

A log is available for all steps processed by learn():

for step in p.log:
print(step)

You can pass the log to the PerceptronPlotter which will recreate the computation visually.

Examples

and

The and-function with a Perceptron.

AABBABA \land B
111
100
010
000
X = np.array([
[0, 0], [0, 1], [1, 0], [1, 1]
])

title= "\"AND\""
y = np.array([0, 0, 0, 1])

p = Perceptron(50)
p.learn(X, y)

plotter = PerceptronPlotter(p.log, X, y, title)
anim = plotter.animate(500)

or

The or-function with a Perceptron.

AABBABA \lor B
111
101
011
000
X = np.array([
[0, 0], [0, 1], [1, 0], [1, 1]
])

title= "\"OR\""
y = np.array([0, 1, 1, 1])

p = Perceptron(50)
p.learn(X, y)

plotter = PerceptronPlotter(p.log, X, y, title)
anim = plotter.animate(500)

xor

The xor-function with a Perceptron.

AABBABA \oplus B
110
101
011
000
X = np.array([
[0, 0], [0, 1], [1, 0], [1, 1]
])

title= "\"OR\""
y = np.array([0, 1, 1, 0])

p = Perceptron(50)
p.learn(X, y)

plotter = PerceptronPlotter(p.log, X, y, title)
anim = plotter.animate(500)

With the Perceptron as a linear discriminant function, the algorithm can not properly create a separator for XOR [📖MIN69]. The Plotter shows the Epoch-label marked as red, which tells that the algorithm was not able to find a separator in 50 epochs.

Cluster Example

The following uses isotropic Gaussian blobs generated by sklearn.datasets.make_blobs. The animate-method is called with an interval of 100 to speed up epoch-runs. The interplay of a larger set of data and the re-adjusting of the separator if accuracy does not reach 1 for a full epoch can be observed nicely.

title = "Clusters"
X, y = make_blobs(n_samples=50, n_features=2, centers=2, cluster_std=2.5)


p = Perceptron(50)
p.learn(X, y)

plotter = PerceptronPlotter(p.log, X, y, title)

anim = plotter.animate(100)


Resources

· 9 min read

Errata für Beweisen lernen (Springer Verlag 2020) von Junk und Treude. Ich hoffe, dass meine Notizen dem Autorenteam zur Überprüfung und ggf. Korrektur nützlich sind.

Zum Hintergrund dieses Blog-Posts gibt es weiter unten mehr Informationen.

Errata

info

Stand 21.06.2023. Meine gesammelten Notizen habe ich komplett überführt. Das Kapitel "D Tipps zu den Übungen" wurde von mir nicht bearbeitet.

Lieber Google-Nutzer, das offizielle Errata findet sich unter https://www.math.uni-konstanz.de/mmath/de/book/material/errata (abgerufen 21.06.2023).

Vergleichslösungen

SeiteFehlerstelleKorrekturvorschlagBemerkung
326 (ML261)0=distd(x,A)infDx,A0 = dist_d(x, A)inf D_{x,A}0=distd(x,A)=infDx,A0 = dist_d(x, A) = inf D_{x,A}
"a<infDx,A+ϵa < inf D_{x,A} + \epsilonu<infDx,A+ϵu < inf D_{x,A} + \epsilon
321 (ML240)und bMin(b)b \in Min(b) gegebenund bMin(B)b \in Min(B) gegeben
318 (ML230)Zu zeigen ist DR:y,zA:d(x,y)D\exists D \in \R: \forall y,z \in A : d(x,y) \le DZu zeigen ist DR:y,zA:d(y,z)D\exists D \in \R: \forall y,z \in A : d(y, z) \le DIn der ML wird weiter d(x,y)d(x,y) genutzt, obwohl sich der Allquantor auf y,zy,z bezieht. Das wäre im Weiteren zu überprüfen, da wir mit der Def. von Brd(x)B_r^d(x) auch d(x,y)<rd(x, y) < r verstehen.
316 (ML226)Wir definieren g:NNR,g: \N_{\le N} \rarr \R,...Wir definieren g:NnR,g: \N_{\le n} \rarr \R,...
315 (ML224)d2(ru,s)=d_2(r \cdot u, s \cdot) =...d2(ru,su)=d_2(r \cdot u, s \cdot u) =...
312 (ML214)Ly={α3β,3α+2β,0)/11+t(0,2,1)tR}L_y = \{\alpha - 3\beta, 3\alpha + 2\beta, 0)/11 + t \cdot (0,2,1)\vert t \in \R\}Ly={3α+2β,α3β,0)/11+t(0,2,1)tR}L_y = \{3\alpha + 2\beta, \alpha - 3\beta, 0)/11 + t \cdot (0,2,1)\vert t \in \R\}u,vu, v vertauscht
308 (ML194, Ende)Da r[u]~r \in [u]_\text{\textasciitilde} auf uXu \in X und u~xu \text{\textasciitilde} x...Da r[u]~r \in [u]_\text{\textasciitilde} auf uXu \in X und u~ru \text{\textasciitilde} r...
302 (ML178 unten)p(f(b),z)Pf,n1(X{a})p(f(b), z) \in P_{f, n-1}(X \setminus \{a\})p(f(b),z)Pf,n(X{a})p(f(b), z) \in P_{f, n}(X \setminus \{a\})
302 (ML178 mittig)[Wegen Aufgabe 153 gilt] xU:Pf,n(X{x})\exists x \in U: P_{f, n}(X \setminus \{x\})xU:Pf,n(X{a})\exists x \in U: P_{f, n}(X \setminus \{a\})Die Menge, auf die Bezug genommen wird, ist hier X{a}X \setminus \{a\}
299 (ML172)zeigt Aufgabe 163zeigt Aufgabe 171
297 (ML168)sei dazu ADαf,n+1A \in D_{\alpha \cdot f, n + 1}sei dazu ADf,n+1A \in D_{f, n + 1}
295 (unten)z+f(b)Sn1(X{a})z + f(b) \in S_{n-1}(X \setminus \{a\})z+f(b)Sn(X{a})z + f(b) \in S_{n}(X \setminus \{a\})
288, 289 (ML147)Es wird auf (3.16) Bezug genommen, aber nN>1:n1N\forall n \in \N_{>1}: n - 1 \in \N ist Axiom (3.18)
277 (ML106)für "    \impliedby" müsste noch yUy \in U gezeigt werden
272 (ML89)Es wird auf eine Symmetrie von \le Bezug genommen, aber in dem Kontext ist \le Antisymmetrisch (Satz 3.11 und Ü89)
269 (ML78)was auf den Widerspruch 010 \ge 1 führtwas auf den Widerspruch 121 \ge 2 führtxNx \in \N, also x0x \ne 0. Im indirekten Beweis wird xx+1x \ge x + 1 mit x=0x=0 verwendet
258 (ML44)mit AA anstelle von AA und BB anstelle von BBmit AA anstelle von EE und BB anstelle von FF

Ideen: Metrische Räume

SeiteFehlerstelleKorrekturvorschlagBemerkung
166 (Ü274)Br(A)B_r(A)Brd(A)B_r^d(A)
167Br(u)B_r(u)Brd(u)B_r^d(u)Mehrfachnennung auf dieser Seite, ohne auf die Metrik Bezug zu nehmen
156 (Ü248)s<supMs < sup MuM:u<supMu \in M: u < sup Mss ist vorgegeben mit sOMs \in O_M, damit gilt ja bereits ssupMs \ge sup M und damit auch sms \ge m
154 (Ü240)Min(b)Min(b)Min(B)Min(B)
147 (Ü226)D:Xn×XnD: X^n \times X^n, ...D:Xn×XnRD: X^n \times X^n \rarr \R, ...

Ideen: Äquivalenzklassen

SeiteFehlerstelleKorrekturvorschlagBemerkung
132 (unten)R([au]~)=R([au]~)R([a \cdot u]_\text{\textasciitilde}) = R([a \cdot u]_\text{\textasciitilde})R(a[u]~)=R([au]~)R(a \boxdot [u]_\text{\textasciitilde}) = R([a \cdot u]_\text{\textasciitilde})

Training

SeiteFehlerstelleKorrekturvorschlagBemerkung
94{tU3:((t1A)(t2B))(t3C)}\{t \in U^3: ((t_1 \in A) \land (t_2 \in B)) \land (t_3 \in C)\}{tU3:(t1A)(t2B)(t3C)}\{t \in U^3: (t_1 \in A) \land (t_2 \in B) \land (t_3 \in C)\}
83 (oben)führt zur Langform U={yZ:xZ:y=g(z)}U = \{y \in \Z: \exists x \in \Z : y = g(z)\}führt zur Langform U={yZ:xZ:y=g(x)}U = \{y \in \Z: \exists x \in \Z : y = g(x)\}in (3.10) wird für die Gleichung ebenfalls die falsche Variable genutzt
82 (Ü102)Zeige aR:g[R0]=Ra\exists a \in \R : g [R_{\ge 0}] = \R_{\ge a}.Zeige aR:g[R0]=Ra\exists a \in \R : g [\R_{\ge 0}] = \R_{\ge a}.

Rechtschreibung / Grammatik / Druckfehler

SeiteFehlerstelleKorrekturvorschlag
325 (ML259)Insebsondere ist distd(x,A)=0dist_d(x,A) = 0Insebsondere Insbesondere ist distd(x,A)=0dist_d(x,A) = 0
295 (ML160)die Argumentation wurde ist dir eventuelldie Argumentation wurde ist dir eventuell
294zu zeigen ist P(A)=2AP(A)\mid = 2^{\mid A \mid}zu zeigen ist P(A)=2A\mid P(A)\mid = 2^{\mid A \mid}
290ergibt m=nANm = \mid n \mid - \mid A \mid \in \Nergibt m=nANm = n - \mid A \mid \in \N
284 (ML132)und mit Aufgabe 132 ergibt sich schliesslichund mit Aufgabe 131 ergibt sich schliesslich
166 (unten)dass sie sich garnicht scheidendass sie sich garnicht scheiden schneiden
149In einer Kugel mit em RadiusIn einer Kugel mit em dem Radius
125 (unten)und mit Aufgabe 179 dannund mit Aufgabe 179 180 dann
120 (oben)Mit Teil (b) von Aufgabe 179 folgt hierausMit Teil (b) von Aufgabe 179 180 folgt hieraus
117Ausgangspizza in a2b2a_2 \cdot b_2 Teile auftrittAusgangspizza in a1b2a_1 \cdot b_2 Teile auftritt
104 (unten)in Für-Alle-Aussage über N0\N_0 zu verwandelnin Für-Alle-AussageAussagen über N0\N_0 zu verwandeln
102 (oben)auf \emptyset gibt es nur ein einzige Funktionauf \emptyset gibt es nur ein eine einzige Funktion
37 (unten)Dies folgt durch Anwendung von Satz 2.11 bei Ersetzung von AA durch EEDies folgt durch Anwendung von Satz 2.11 2.9 bei Ersetzung von AA durch EE
37 (unten, folgt der vorher erwähnten Fehlerstelle)Dies folgt durch Anwendung von Satz 2.9 bei ErsetzungDies folgt durch Anwendung von Satz 2.9 2.11 bei Ersetzung

Anmerkungen

SeiteBemerkung
148 Definition 5.9vielleicht bietet es sich hier bereits an, in der Definition den Begriff "offene Kugel" zu verwenden
118 Definition 4.1Sei ~\text{\textasciitilde} eine Äquivalenzrelation auf einer nicht leeren Menge XX
91 Definition 3.24Informatiker würden sich hier über die Erwähnung "partielle Funktion" freuen
*ML = Musterlösung*Ü = Übung

Hintergrund: Aufgabe 178 und das kleine Manöver, das kostete

In der Lösung zu Aufgabe 178 aus Beweisen lernen - und der hierzu vorbereitenden Aufgabe 158 - bin ich bei der Nachbereitung des Lösungsvorschlages nicht zu dem gleichen Ergebnis gekommen - der Definitionsbereich einer Funktion wurde falsch angegeben. Den Versuch, die Falschaussage nachzuweisen, habe ich hier in diesem Post dokumentiert.

Weitere Notizen bzgl. eventueller Fehler hinsichtlich Logik- und Druck fasse ich in dem o.a. Errata zusammen.

Aufgabe 158

Notation

UU: Elementuniversum

En\Epsilon_n: Menge aller endlichen Mengen mit der Mächtigkeit nn

P(K)P(K): Potenzmenge von KK mit KUK \subset U

Aufgabenstellung

Es ist per Induktion zu beweisen, das

nN0:k(N0)n:XDk:Sk(X)=1\forall n \in \N_0: \forall k \in (N_0)_{\le n}: \forall X \in D_k: | S_k(X)| = 1

Folgendes steht mit den Voraussetzungen zur Verfügung:

f:XRf: X \mapsto R

Dn:={XEn:XDef(f)}D_n:=\{X \in \Epsilon_n : X \subset Def(f) \}

S0:D0P(R), X{0}S_0: D_0 \to P(\R),\space X \mapsto \{0\}

Sn+1:Dn+1P(R), X{f(x)+s  (x,s)X×Sn(X{x})}S_{n+1}: D_{n+1} \to P(\R),\space X \mapsto \{f(x) + s \space | \space (x, s) \in X \times S_{n}(X \setminus \{x\})\}

Induktionsschritt

Die Autoren wollen die Eindeutigkeit des Elementes xU:xSn+1(X)x \in U: x \in S_{n+1}(X) über

!xU:xSn+1(X)\exists! x \in U: x \in S_{n+1}(X)

zeigen. Hierzu muss die Existenz und die Eindeutigkeit des Elementes gezeigt werden, so dass wegen u,vSn+1(X):u=v\forall u,v \in S_{n+1}(X): u = v auch Sn+1(X)=1|S_{n+1}(X)| = 1 folgt (u.a. wegen Aufgabe 99 und Aufgabe 153).

Argumentation

Hierzu sei

u:=f(a)+s,v:=f(b)+tu:= f(a)+s, v:= f(b)+t

Die Autoren zeigen einige Schritte weiter, dass mit der Induktionsvoraussetzung für ss folgt:

Da f(a)+sSn+1(X)f(a) + s \in S_{n+1}(X), ist sSn(X{a})s \in S_n(X \setminus \{a\}).

Mit bX{a}b \in X \setminus \{a\} soll dann s=f(b)+zs = f(b) + z gezeigt werden, wobei wieder die Induktionsvoraussetzung angewendet wird und zSn1(X{a}{b})z \in S_{n-1}(X \setminus \{a\} \setminus \{b\}) gefunden wird.

Fehlerstelle

In einem weiteren Schritt wird dann behauptet, dass f(b)+zSn1(X{a})f(b) + z \in S_{n-1}(X \setminus\{a\}) ist, und deswegen f(b)+z{s}f(b)+z \in \{s\} und folglich f(b)+z=sf(b) + z = s. Das scheint der Fehler zu sein, denn für ss wurde gezeigt: sSn(X{a})s \in S_n(X \setminus \{a\}):

Wenn sSn(X{a})s \in S_n(X \setminus \{a\}) und sSn1(X{a})s \in S_{n-1}(X \setminus\{a\}) gelten würde, dann würde für

f(c)+sSn(X{a})f(c)+s \in S_n(X \setminus \{a\}) und f(b)+tSn1(X{a})f(b)+t \in S_{n-1}(X \setminus \{a\}) auch f(c)+s=f(b)+tf(c)+s = f(b)+t gelten (für c,bX{a}c, b \in X \setminus \{a\}).

Da s=f(b)+ts = f(b) + t wegen f(b)+tSn1(X{a})f(b) + t \in S_{n-1}(X \setminus \{a\}) und sSn1(X{a})s \in S_{n-1}(X \setminus\{a\}) folgt dann auch f(c)+s=sf(c) + s = s, was im Widerspruch zu f(c)+s=f(c)+sf(c) + s = f(c) + s steht und offensichtlich nicht cX{a}\forall c \in X \setminus \{a\} gilt.

· 8 min read
info

Ein Kommentar zu einem Kommentar zu Eberhard Wolff's Episode 159 - Big Ball of Mud als Teil von Software-Architektur im Stream. Eine englische Übersetzung findet sich hier.

Verfällt ein Big Ball of Mud?

Durch den Fortschritt der Technologie und der Arbeit von Leuten wie Brooks, Buschmann und Booch wurde uns Entwicklern der Weg vom mikroskopischen ins makroskopische geebnet. Lang vorbei sind die Zeiten, in denen schrankhohe Rechnersysteme nah an der Infrastruktur programmiert werden mußten.
Jedoch, wer heute den Mythischen Mann-Monat [📖MMM] liest und über die damals zur Verfügung stehende Technik schmunzelt, der wird spätestens bei der Aktualität der anderen erwähnten Probleme betreffs Organisation und Planung von Projekten rasch in die Gegenwart zurückgeworfen. Aus Eskapismus wird ein erhobener Finger: Das Lesevergnügen mahnt plötzlich zur Reflektion. Die Probleme von damals sind heute immer noch aktuell, und die Entwicklung der Technik verlief bis dato offensichtlich ungleich schneller als die von Planung und Organisation.

Die Motivation und das Wissen darum, wie man heutzutage Schablonen für die Erstellung von Objekten und Klassen nutzt und all seine Erfahrung in das Schneiden und Zusammenstecken derselben zur Abstraktion einer Fachlichkeit einfliessen lässt, ist dann nicht zuletzt auch der Gang of Four [📖GOF] zu verdanken, die Entwurfsmuster en vogue gemacht haben und in einer ganzen Generation von Programmierern das Interesse an Software Design zu wecken wussten. Aber: Der Schreiner mag in der Lage sein, einen Satz Fensterrahmen passend zu dem äusseren Erscheinungsbild des Hauses zu zimmern. Das hübscheste Fenster hilft aber nichts, wenn niemand weiss wie man es einbaut, geschweige denn öffnet und wieder schliesst.

Wir machen den gedanklichen Sprung zurück in unsere Domäne und wissen: Solche Elemente werden dann in Menge problematisch, wenn ihre Vereinigung in einem System funktional sein und natürlich ein möglichst wartbares Gesamtgebilde ergeben soll. Auch hier helfen Erfahrung und bewährte Blaupausen, damit sich Entwickler*in nicht in einem undurchdringlichen Dickicht von Verantwortlichkeiten und Assoziationen verliert.
Leider gelingt das nicht ganz so oft so gut. Wenn wir nach einem frischen Pull über das Sein des Spaghetti-Codes eines Kollegen sinnieren, oder wir uns selbst dabei ertappen, Schichten durch das freitag-mittagliche Voranstellen eines new vor einer low-level-Klasse in einer high-level-Klasse zu durchbrechen, dann ist man ihm schon einen Schritt näher, dem berüchtigten Big Ball of Mud (BBOM), den Eberhard Wolff in der Folge 159 seiner Reihe Softwarearchitektur im Stream mit gewohnter Präzision vorgestellt und in Ursache und Wirkung analysiert hat.

In der Folge beruft er sich auf das Paper von Foote und Yoder, in dem - vor über 20 Jahren - die Frage gestellt wurde, inwieweit denn so ein Big Ball of Mud überhaupt ein Anti-Pattern sei: Das man diese quellcodegewordene Negation einer Struktur so häufig in Systemen vorfindet sollte doch eigentlich den Schluss zulassen, dass es sich hierbei gar nicht um ein Anti-Pattern, sondern gegebenenfalls um ein erprobtes und bewährtes Konzept in der Software-Entwicklung handelt, nämlich das des geringsten Widerstandes. Dieser kennzeichnet sich hier durch die Vermeidung von Up-Front Architektur. Stattdessen richtet sich der Fokus direkt auf die Umsetzung von Features und Funktionalität, auch, aber nicht ausschließlich, wenn Architektur als zu vermeidender Kostenfaktor verstanden wird:

"Therefore, focus first on features and functionality, then focus on architecture and performance." [1]

Man könnte daraus schließen, man solle mehr Verständnis für den Entwickler zeigen, der diesen Weg wählt oder wählen muß. Auch, wenn infolgedessen der Ansatz eines durch die Zuarbeit verschiedener Teams entstehenden Software-Fundamentes über das Fehlen von allgemein als geschäftswertig erachteter Best Practices mit jedem Commit ein bisschen mehr verhindert oder aufgelöst wird. Die Frage hat wohl auch Foote und Yoder beschäftigt:

"[…] we seek not to cast blame upon those who must wallow in these mires. In part, our attitude is to ‘hate the sin, but love the sinner‘". [1]

Wenn der Big Ball of Mud als Konsequenz dieses Konzeptes als Struktur eines Systems erkannt wird, das keine Struktur beinhaltet, dann können wir ex falso quodlibet auch jede beliebige Aussage als gültig annehmen, wenn wir uns bei der Beschreibung dieses Systems darauf berufen, dass diesem System eben eine Struktur innewohnt: Und also ist ein Big Ball of Mud eben ein Entwurfsmuster. Aber! So ein Gebilde bekommt man ganz gut beliebig hin, so wie ein Zimmermann sicher auch ohne Kenntnis darüber, wie man Mörtel anrührt, irgendwie in der Lage sein wird, Ziegelsteine um seine Fenster herum zu stapeln.

Unter gewissen Umständen kann das bewusste Zulassen zunehmender Entropie in einem Software System dabei helfen, Kontexte zu identifizieren und die Fachlichkeit zu verstehen, um Schichten herauszumeisseln und Grenzen zu schneiden. Evans, Fowler und auch Foote und Yoder sind sich in jedem Fall einer Sache sicher: Refactoring muss ständig erfolgen, um nicht die Kontrolle zu verlieren.

"The way to arrest entropy in software is to refactor it." [1]

Dabei ist man sich aber auch des zweiten Hauptsatzes der Thermodynamik bewusst: Die Entropie kann nicht abnehmen, sie kann gleich bleiben, oder sie kann zunehmen. Will man letzteres verhindern, rät Evans dazu, den BBOM zu demarkieren:

"Draw a boundary around the entire mess and designate it a big ball of mud. Do not try to apply sophisticated modeling within this context. Be alert to the tendency for such systems to sprawl into other contexts." [📖DDDR, p. 38]

Foote und Yoder haben eine ähnliche Empfehlung, die sie in dem Paper etwas schwungvoller mit "Sweeping it under the rug" bezeichnen:

"Therefore, if you can’t easily make a mess go away, at least cordon it off. This restricts the disorder to a fixed area, keeps it out of sight, and can set the stage for additional refactoring." [1]

Egal ob Grenzen gezogen werden oder man den BBOM unter den Teppich schaufelt: Eben so kommen wir über grobgranulare Schnittstellen an ausgewählte Funktionalität, und wir lassen gleichzeitig nicht zu, dass die zähe Masse aus dem BBOM in unser System tropft und dort Gestalt annimmt (oder eben auch nicht). Konsequent katalogisiert Robert C. Martin dann auch Viscosity in die Kategorie Design Smell ein [📖ASD, p. 88].

Mein Kommentar während der Folge lautete, dass es in Anbetracht all dessen ohnehin erschwerend hinzukommen kann, dem Management die Sinnhaftigkeit von Tests zu vermitteln. Der Antwort von Eberhard Wolff darauf entnahmen ich, dass in den von ihm beschriebenen Szenarien Tests a priori als sinnvoll verstanden werden und damit Teil der Entwicklung sind (zumindest aber Tests durch entsprechende Fachkräfte): Umso wichtiger sind diese Tests, wenn sich schon zu Beginn des Projektes zeigt, dass wegen fehlender Architekturplanung und wahrscheinlich diffuser Funktions- und Modulgrenzen Funktionalität sichergestellt werden muss.

Von dieser Implikation bin ich in meinem Kommentar nicht ausgegangen. Was ich meinte, war: Wenn Architektur keinen Geschäftswert hat, und dies zu einem BBOM führt, dann kann das auch zu dem Broken Window Effekt führen. Hunt und Thomas raten dazu: "Dont live with broken Windows." [📖PP, p. 7], und Foote und Yoder beziehen aus ähnlichen Erfahrungen die Ensicht:

"If such sprawl continues unabated, the structure of the system can become so badly compromised that it must be abandoned. As with a decaying neighborhood, a downward spiral ensues." [1]

Wenn Geld und Zeit in einem Projekt knapp sind, und Architektur damit einhergehend als nicht zielführend verstanden wird, dann ist die Wahrscheinlichkeit eher nicht gering, dass auch das Testing der Software – ich meine hiermit die Art von Tests, die der Entwickler selber schreibt, um sein System zu verifizieren - ebenfalls als negativer Kostenfaktor geführt wird. Sollte das Gegenteil der Fall sein, dann könnte die fehlende Architektur und der entstehende BBOM das eingeworfene Fenster in der Nachbarschaft sein, das dazu führt, dass noch mehr Fenster eingeworfen werden. Der Entwickler, der sich bewusst nicht innerhalb der Schichten bewegt, sondern vor allem dazwischen, sieht sich dazu veranlasst, seinen Code nicht durch Tests zu dokumentieren, weil er dem System die Sinnhaftigkeit ob der fehlenden Struktur aberkennt. Die Projektbeteiligten akzeptieren ein eingeworfenes Fenster wahrscheinlich eher, wenn daneben schon eins existiert.

Wenn alle Projektbeteiligten sich darauf verständigen, dass Grenzen und Fachlichkeiten auch durch unstrukturiertes, organisches Wachstum erkannt werden können, und das System erst später "ent-steht", können Strukturen also später nachgezogen werden: Letztendlich ist eine zähe Masse etwas Formbaren ähnlich, und die Dynamik unserer Handwerkskunst steckt in dem Namen Software. Besteht das Fundament möglichst nicht aus einem Throw Away, dann sollte auch allen Projektbeteiligten die Notwendigkeit von Tests klar sein: Die Räson aller Verantwortlichen verhindern somit ein erstes eingeworfenes Fenster, und es ist an Fachexperten und Programmierern, dass es nicht zu weiteren kommt.


References

· 8 min read
info

A comment on a comment to Eberhard Wolff's recent episode 159 of Software-Architektur im Stream - Big Ball of Mud. This is a translation of this article, which was originally published in german language.

While the pioneers of computer science had to program computer systems close to the infrastructure, as technology progressed and thanks to the tireless work of people like Brooks, Buschmann and Booch, we found the way from the microscopic to the macroscopic.
However, if you read the Mythical Man Month [📖MMM] today and smile about the technology that was available at the time, you will quickly be thrown back to our present time, where problems regarding the organization and planning of projects persist. A raised finger suddenly calls for reflection: The problems of that time are still relevant today. Obviously, the development of technology has been much faster than that of planning, organization and realization of projects.

The motivation and knowledge of how to use templates to create objects and classes, and how to use all of our experience for cutting and assembling them into abstractions of a technicality, has gained momentum since the Gang of Four [📖GOF] sparked an interest in software design in a generation of programmers. But although the carpenter may be able to carve a set of window frames to match the exterior of the house, the prettiest window is of no use if nobody knows how to install it, let alone open and close it.

In our domain, such elements become problematic when their combination is supposed to be functional, and if it should resemble a maintainable structure as a whole. Experience and proven blueprints help to ensure that developers do not lose themselves in a jungle of tangled responsibilities and associations when integrating such elements.

Unfortunately, that doesn't always work out so well. When we catch ourselves breaking layers by adding a new in front of a lower-level class in a high-level class, we are one step closer to the notorious Big Ball of Mud (BBOM), which Eberhard Wolff presented and analyzed in episode 159 of his series "Software Architecture im Stream" with his usual precision.

In this episode, he also refers to the paper of Foote and Yoder, in which - more than 20 years ago - the question was asked to what extent such a Big Ball of Mud is an anti-pattern: That this negation of a structure is so often found in systems should actually lead to the conclusion that this is not an anti-pattern at all, but rather a tried and tested concept in software development, namely that of least resistance. This is characterized here by the avoidance of up-front architecture. Instead, the focus is directly on the implementation of features and functionality, also, but not exclusively, if architecture is understood as a cost factor to be avoided:

"Therefore, focus first on features and functionality, then focus on architecture and performance." [1]

A conclusion could be that we should show more understanding for the developer who chooses or must choose to develop like this. Even if such an approach prevents or dissolves a solid fundament for a software system due to the lack of best practices that are generally considered to be valuable for business. Foote and Yoder were probably also concerned with the question:

"[…] we seek not to cast blame upon those who must wallow in these mires. In part, our attitude is to ‘hate the sin, but love the sinner‘". [1]

If the Big Ball of Mud is ultimately itself a structure that contains no structure, similar to how the empty set is itself a set, then we can ex falso quodlibet accept any statement as valid if we assume that such a system has an inherent structure: And so a Big Ball of Mud is a design pattern.

But! A structure like this can be done quite easily with no experience, just as a carpenter will probably be able to stack bricks around his window without any knowledge of how to mix mortar.

Under certain circumstances, however, consciously allowing entropy to take over in a software system can also help to identify contexts and understand the technicalities in order to carve out layers and cut boundaries. In any case, Evans, Fowler and also Foote and Yoder are sure of one thing: refactoring must be done constantly in order not to lose control.

"The way to arrest entropy in software is to refactor it." [1]

However, one is also aware of the second law of thermodynamics: entropy cannot decrease, it can remain the same, or it can increase. If you want to prevent the latter, Evans advises to create a boundary around the BBOM:

"Draw a boundary around the entire mess and designate it a big ball of mud. Do not try to apply sophisticated modeling within this context. Be alert to the tendency for such systems to sprawl into other contexts." [📖DDDR, p. 38]

Foote and Yoder have a similar recommendation, which they more eloquently call "Sweeping it under the rug" in their paper:

"Therefore, if you can’t easily make a mess go away, at least cordon it off. This restricts the disorder to a fixed area, keeps it out of sight, and can set the stage for additional refactoring." [1]

It doesn't matter whether the BBOM is shoveled under the carpet or safe boundaries are created: It allows us to get selected functionality via coarse-grained interfaces out of the BBOM, and at the same time we don't allow the viscous mass to drip into our system.

Consequently, Robert C. Martin also cataloged Viscosity in the category Design Smell [📖ASD, p. 88].

My comment during the episode was that, given all of this, communicating the value of testing to management can be an added complication. I gathered from Eberhard Wolff's answer that in the scenarios he described, tests are understood to be useful a priori and are therefore part of the development process: It is mandatory to verify functionality due to the lack of architectural planning, resulting in diffuse modular boundaries.

I did not assume this implication in my comment. What I meant was: If architecture is seen as a cost factor or other conditions prevail that prevent architecture, and thus leads to a BBOM, then this can also lead to the Broken Window Effect. Hunt and Thomas have already advised: "Don't live with broken Windows." [📖PP, p. 7], and Foote and Yoder conclude from similar experiences:

"If such sprawl continues unabated, the structure of the system can become so badly compromised that it must be abandoned. As with a decaying neighborhood, a downward spiral ensues." [1]

If money and time are tight in a project and the architecture is not understood to be of value, then there is a probability that testing the software - I understand this as the kind of tests that the developer writes for verifying his code - is also seen as a negative cost factor. If the opposite is true, then the missing architecture and the resulting BBOM could be the broken window in the neighborhood, causing even more windows to be smashed. The developer who consciously does not move within the layers, but in between, feels compelled not to document his code through tests because he may fail to see any value of his work in the end. Those involved in the project are more likely to accept a broken window if there is already one next to it.

If everyone involved in the project agrees that limits and technicalities can also be recognized through unstructured, organic growth, and the system only emerges later, structures can added later: Ultimately, mud is a mass that is malleable, and the dynamic of our craftsmanship is in the name software. If the foundation does not consist of a Throw Away, then all those involved in the project should be aware that testing is required: the rationale of all those responsible prevents the first window being thrown in, and it is up to the technical experts and programmers to ensure that there won't ever be any.


References

· 3 min read

bcc-header issues with Horde_Mime_Mail

This issue caused some uncertainty since I was not sure if the headers were broken due to missing quotes. See https://github.com/conjoon/php-lib-conjoon/issues/17.

Turns out that the way I assembled an email from a full text and converting it back to an instance of Horde_Mime_Mail does not consider the type of the internal representation of the bcc-field properly. It's related to Horde_Mail_Rfc822_List::_normalize and how values passed from Horde_Mime_Mail::send() are processed by it.

Here's a code snippet that shows how I use a full text message as input, then converting it back to an instance of Horde_Mime_Mail with headers processed by Horde_Mime_Headers::parseHeaders(). The original message has a bcc header-field:

(Original code can be found here).

HordeClient.php
         $target = $item->getFullMsg(false);
// ...
$headers = Horde_Mime_Headers::parseHeaders($target);

$mail = new Horde_Mime_Mail($headers);
$part = Horde_Mime_Part::parseMessage($target);
$mail->setBasePart($part);

$mailer = $this->getMailer($account);
$mail->send($mailer);

Horde_Mime_Mail temporarily removes the bcc header and stores it in a property named _bcc, then uses this value to add it to the recipients' addresses later on in send(). This is so the bcc-header is not appearing in the source of the message the recipients receive (see https://www.ietf.org/rfc/rfc2822.txt, Section 3.6.3 and 5):

"The "Bcc:" field (where the "Bcc" means "Blind Carbon Copy") contains addresses of recipients of the message whose addresses are not to be revealed to other recipients of the message." https://www.ietf.org/rfc/rfc2822.txt, Section 3.6.3

This is a part of Horde_mime_Mail::send():

Horde/Mime/Mail.php

/* Build recipients. */
$recipients = clone $this->_recipients;
foreach (array('to', 'cc') as $header) {
if ($h = $this->_headers[$header]) {
$recipients->add($h->getAddressList());
}
}
if ($this->_bcc) {
$recipients->add($this->_bcc);
}

The source above shows that for to / cc the method getAddressList() is being called, while the value of _bcc gets passed to the add() method. However, _bcc holds in this case an instance of Horde_Mime_Headers_Addresses, which Horde_Mail_Rfc822_List::_normalize() does not consider. The value is ultimately ignored, Emails are not being sent to the addresses mentioned in the bcc header.

Here's the implementation of normalize():

Horde/Mail/Rfc822/List.php

protected function _normalize($obs)
{
$add = array();

if (!($obs instanceof Horde_Mail_Rfc822_List) &&
!is_array($obs)) {
$obs = array($obs);
}

foreach ($obs as $val) {
if (is_string($val)) {
$rfc822 = new Horde_Mail_Rfc822();
$val = $rfc822->parseAddressList($val);
}

if ($val instanceof Horde_Mail_Rfc822_List) {
$val->setIteratorFilter(self::BASE_ELEMENTS);
foreach ($val as $val2) {
$add[] = $val2;
}
} elseif ($val instanceof Horde_Mail_Rfc822_Object) {
$add[] = $val;
}
}

return $add;
}

A possible fix is to call getAddressList() on _bcc in Horde_Mime_Mail::send() or check for this type in the normalize()-method of Horde_Mail_Rfc822_List:

Horde/Mail/Rfc822/List.php.diff

protected function _normalize($obs)
{
$add = array();


+ if ($obs instanceof Horde_Mime_Headers_Addresses) {
+ $obs = $obs->getAddressList();
+ }

if (!($obs instanceof Horde_Mail_Rfc822_List) &&
!is_array($obs)) {
$obs = array($obs);
}

Fixing this in Horde/Mime/Mail.php is also possible, although I do not know if that would cause any side effect since I could not find the expected type of _bcc. It gets checked in '_normalize()' (see above) so I guess this would be the better place to apply the fix, instead of doing this:

Horde/Mime/Mail.php.diff

/* Build recipients. */
$recipients = clone $this->_recipients;
foreach (array('to', 'cc') as $header) {
if ($h = $this->_headers[$header]) {
$recipients->add($h->getAddressList());
}
}
if ($this->_bcc) {
- $recipients->add($this->_bcc);
+ $recipients->add($this->_bcc->getAddressList());
}

Update 21.03.2023: PR available here

· 3 min read

Fix: Missing favicon.ico in Sencha ExtJS production builds

Fixing missing favicon.ico in Ext JS applications.

The original issue is tracked here: https://github.com/conjoon/conjoon/issues/24

The issue

I’m not quite sure when and why it broke, but it looks like production builds of Sencha Ext JS applications do not contain any favicon.ico originally existing in the development build (anymore).

While everything seems to be okay with development builds (that’s easy, they refer to the development’s root folder in most cases and do not copy and move files around like the production build does), deploying to production will show the default icon coming with your vendor’s browser for any Sencha ExtJS application, at least when your environment is using the following package versions:

    webpack v4.39.3n/a
ext-webpack-plugin v7.6.0
Ext JS v7.6.0.41
Sencha Cmd v7.6.0.87
``

A call to

```bash
$ cross-env webpack
--env.profile=desktop
--env.environment=production
--env.treeshake=yes
--env.cmdopts=--uses

will produce the production build, but the production build will not contain the favicon.ico. Here’s proof (… sort of):

The fix

I did not invest too much investigating the reason why this file is omitted. Instead, I added the copy-webpack-plugin to the project and made sure the the favicon is copied over when running npm run build.

If you’re reading this post, you most likely stumbled across the same issue. Here’s how to fix it.

  1. Add copy-webpack-plugin to your project

I’m still sporting webpack v4.39.3 so I had to fall back to an older version of the plugin. I’m using copy-webpack-plugin@6.4.1 in this case:

    $ npm i -D copy-webpack-plugin@6.4.1
  1. Add a few more modules to your project’s webpack.config.js

Add the following two lines to the head of the file:

    // ....
const CopyWebpackPlugin = require("copy-webpack-plugin");
const fs = require("fs");
//...

Why fs? I couldn’t find an easy way to access the application’s name at this point of the build step through the variables available, so I’m using fs to parse the project's app.json. The value of its name-property is then used for computing the destination folder for the copy-operation.

  1. Add the copy-webpack-plugin to the list of plugins in the script

You’ll easily find the location that has to be edited when looking for the @sencha/ext-webpack-plugin-configuration:

    const plugins = [
new ExtWebpackPlugin({
// ...
}),
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname, './favicon.ico'),
to: path.join(
__dirname,
"build",
environment,
JSON.parse(
fs.readFileSync(
path.resolve(__dirname, './app.json')
)
).name
)
}]
})
]

Subsequent builds will now contain the favicon.ico.

· 2 min read

Releasing conjoon V1.0

I’m happy to announce conjoon 1.0, the very first major release of the Open Source JavaScript Email client.

For updating to the latest version, simply use our installer. It will let you select the latest release when opting for the version to install.

Highlights

v1.0.0 marks the first major release for our JavaScript Email frontend, over 100 tickets related to bugfixes, optimizations and minor features across all projects where closed.

This release focuses on providing a stable frontend in conjunction with lumen-app-email.

Besides the features already introduced with the release candidates, the following features have been added:

Plugins

Installer and CLI actions for lumen-app-email

The installation for lumen-app-email has been simplified with the help of Artisan and CLI commands. To get an instance of lumen-app-email running, use

$ composer create-project conjoon/lumen-app-email {targetDir} {version}

which will start the installation process. For more information, refer to the official guide.

Docker Container

ddev-ms-email has been updated to utilize the installer of lumen-app-email and additionally provides integration options for conjoon so that the container can be used for serving both the backend and the frontend.

$ ddev create-conjoon

will start the installation of conjoon. For more information, refer to the official guide.

Happy coding! 🎈

· 3 min read

Easily create Siesta tests for your applications

Siesta is a JavaScript unit and UI testing tool originally written by Mats Bryntse which allows for running tests (for Ext JS (view-) components, amongst others) directly in the web browser (or headless in case you want to use it with your ci tools).

It is conjoon’s favorite among the various testing tools out there (we’re also working with Jest🃏 when there’s no Ext JS involved) and without it, it’s clear that End-to-End tests of some of the features and changes that have made it into conjoon would have slowed the project down due to their cyclic complexity — or even worse, make the software stuck in regression.

extjs-app-webmail alone sports more than 5000 unit and ui tests created with Siesta and they all make sure that the frontend behaves as intended and is free from unwelcomed side effects for any edge case that might occur (we know that’s a bold statement).

To ease the process of setting up a functional Siesta environment, we’re introducing the cli tool create-siesta which can be used with any JavaScript framework that requires a functional Siesta application running in a web browser, or at least a fully fledged infrastructure for running Siesta tests. However, by providing integrated build options for the Ext JS SDK, it is best suited for environments where the Sencha framework is already being used or will be used.

For using the tool, all that is required is a working Node.js installation on your machine. The scaffolding process can then be started by typing

$ npx create-siesta@latest

on the console. create-siesta will then guide you through the installation process and also consider the environment (aka current working directory) it was invoked in, by falling back to already existing Ext JS sources for example, or any other package requirements already available.

Once create-siesta is finished, a folder (defaulting to tests) will be available with your project that contains a scaffolded Siesta environment with templates for additional and future tests you and your team can implement. Tests can then be started with

$ npm run siesta:test

create-siesta builds upon an already available helper tool for creating tests with Siesta, namely **@coon-js/siesta-lib-helper which is already widely used throughout the conjoon** project and the packages it depends on. This means that you will have an additional control at hand with the Siesta application that allows for switching between toolkit dependent tests and dynamically changing timeout values used with

    t.waitForMs()

in tests.

*The additional control made available by siesta-lib-helper allows for switching between the modern M and classic C toolkit and provides a list of timeout values that can be globally used with t.waitForMs() in tests.*

Documentation

The documentation for create-siesta can be found here, the sources are available with the coon.js organization at GitHub, which provides a collection of useful tools for rapid Sencha Ext JS application development, and spawned from the conjoon open source project.

· One min read

JavaScript, function-as-object and the internet

The internet ™️ has caught up on an ad by FERCHAU, found — amongst others — somewhere within the depths of the Berlin subway.

Some devs cringe at the code used with the ad — turns out it can easily get de-mystified with JavaScript’s function-as-object style:

    const careerStuck = () => {};
careerStuck.stop = () => {};

Opinions regarding semantics may differ:

    if (careerStuck() === true) {
careerStuck.stop();
beSmart(); // 👀
}

Further reading

Martin Fowler on FunctionAsObject in an article from 2017. The pattern goes back to the last century when Eugene Wallingford coined the name “Function as Object” in his 1999 pattern language “Envoy”.

· 2 min read

conjoon installer updates

Creating a custom labeled version of conjoon has never been easier.

The installer (i.e. initializer) for conjoon received an update to make setting up a local installation of the frontend more convenient and more intuitive.

The installer requires Node.js 16.14 or above and is started by typing

$ npx create-conjoon@latest

on the command line. Additionally, you have the option to initiate the quick install right away by providing the name and the target directory for the installation:

$ npx create-conjoon@latest "my webmail" ~/webmail

The quick install will continue the installation process by picking the latest release from the package registry (including pre-releases) and configure the installation with fake endpoints returning demo data. You can still connect to production systems later on by using appropriate settings.

If you prefer a guided installation for configuring endpoints for existing backends right away, you can omit any of the arguments. The installer will then present various options to make sure your requirements are met!

In both cases, target directory will contain the files required for developing conjoon and a production build available in the build folder, which is served by typing npm run stage in the target directory.