Posts mit dem Label TDD werden angezeigt. Alle Posts anzeigen
Posts mit dem Label TDD werden angezeigt. Alle Posts anzeigen

Freitag, 27. November 2015

Thoughts On "SE-Radio Episode 242: Dave Thomas on Innovating Legacy Systems"

In episode 242 Software Engineering Radio interviewed Dave Thomas about how to deal with legacy systems. I liked the show so much that I had to do a sketchnote:

Controversial And Very Inspiring At The Same Time - SE Radio 242 with Dave Thomas

Actually I am a faithful follower of Working Effectively With Legacy Code : isolate the piece of code you want to change (dependency breaking), write tests for it and then modify the code using TDD. Over time I got quite good at it - even in C. However, it's a lot of effort - even when you're trained.

Dave sayed "Unit tests are a waste of time, focus on acceptance test" (end-to-end tests). The problem with end-to-end tests is that they are even harder to setup. Instead of mocking the objects around you, you have to provide all the external dependencies or at least good replacements:  test databases, test middleware, test clients...
Anyway, once you've managed all that and wrote your first end-to-end test, things are getting easier a lot. Covering "unhappy paths" with tests is now actually quite simple - drop a central database table, switch of the middleware, send faulty messages to your application and check what's going on.

With all this virtualization (docker as latest hype) and infrastructure as code (Puppet, Chef, ...) we now have got good tools to write end-to-end tests which are repeatable, automated and maintainable.
Surely this was not as simple in 2004 when "Working Effectively With Legacy Code" came out.

Dave's statements  reminded of the Golden Master approach which is quite similar. However, the initial end-to-end tests there is only meant to  provide the basic safety net towards a unit test coverage. The latter one is the actual goal of "Golden Master" testing.

So yes, maybe going from outside to inside is nowadays a better way of creating a safety net. I am still not convinced to ditch unit testing of old code completely but this is as always something you have to try out.

Sonntag, 25. Oktober 2015

Slides For "Not Your Fathers C - C Application Development in 2015"

Before going on a too short vacation I attended the Developer Open Space conference in my home town Leipzig. As the conference name suggests this was an Open Space conference where the participants them self create the agenda for the day.

I held a session on modern C development which was a high level summary of my past posts here. The slides are now online:

Samstag, 26. September 2015

Socrates 2015 - What the f**k

I'm a IT professional for over a decade now - but I've never attended something like this before:

A colleague and me took part at the Socrates 2015 conference. It's an Open Space conference which mean there is no agenda at the beginning. Just an empty flipchart which if filled with self-proposed topics of the participants at the beginning of each conference day.
No pop star like speakers but people like you and me how admit "I am not the expert but I've done something in that field and I want to share this knowledge with you." For me this is all you need - for everything more involved there are books.

While thinking about the conference I was astonished how much I've either learned or was pushed towards something. Here is my top 5:

  1. Sketchnotes. This is for me "Doing something more with the whiteboard than the usual stuff without needing to be an artist." I believe in the area of Powerpoint spoilt organisations  it makes a huge difference to develop a topic just with a flipchart and a pen. It's amazing how much you can do. I love it.
  2. Walking Skeleton. I never heard of this topic before. It's basically an approach to build up a system not from inside out but from outside to the inside, guided by so called end-to-end tests as well as unit tests. The book to read on that topic is Growing Object Oriented Software Guided By Tests. People refer to the approach as "London School Of TDD" (the authors of that book are based in London) in contrast to the "Chicago School Of TDD" which is TDD as we know it (Kent Beck, Uncle Bob). Really interesting.
  3. Personal Kanban. I was once again pushed to think about my time management method. I attended a session about Knowledge Management but we soon found out the the key is actually Time Management. So one of the attendees offered a session about time management the next day. It all was about the guys Getting Things Done interpretation. Anyway, during the discussion I was reminded on the existence of Personal Kanban, and this is what I am trying out at the moment, all analog!. There will be a post with a nice picture somewhen later.
  4. Legacy Code Retreat. I spent quite some time wadding into muddy legacy C code. I thought I am quite good at this topic but this guy who hosted a 2 hour session an how to attack lagacy code was somewhere else. Really impressive. I've done some handson training for a Golden Master Test. Also, training people to deal with legacy code can be simplified by using Adrians work.
  5. I had a chat with somebody at dinner how was working for a software consultancy run as a cooperative. Basically, they have no boss and decide all together how they want the company to proceed. This was really thrilling, I was just about to hand in my CV but they are based in the wrong city ;-).
Overall the conference was much more than I expected. In my hometown Leipzig/ Germany there will be the Developer Open Space conference in some weeks, run also as an Open Space event - this time I plan to prepare something my self. Working title "Not Your Father's C".

Yes - there were also some hippies ;-)


Dienstag, 4. August 2015

Uncle Bob On Videos

For those of you who are even later than me: Uncle Bob, famous for his Clean Code book has produced a series of one hour videos which cover the topics of the book - but in a very funny and at the same time educational way: Clean Code Videos

Freitag, 31. Juli 2015

Dependency Breaking in bash

In times of Devops you might want to also (unit) test your shell scripts. A collegue of mine introduced me to a nice trick how to do dependency breaking with bash.

The technique is based on the declare -f command which allows you to print the definition of previously defined function:

$ sayHello() { echo "hello world";}
$ declare -f sayHello
sayHello () 
{ 
    echo "hello world"
}

The following script sketches how to utilize this feature to replace an original function with a stub at run time:
#!/bin/bash

doComplicatedThings() {
    echo "i am doing many complicated things"
    # ...
}

codeOfOriginalFunction=$(declare -f doComplicatedThings)

# stub out "doComplicatedThings"
function doComplicatedThings { :; }

# executes stub (does nothing)
doComplicatedThings

# re-establish orignal function
eval "${codeOfOriginalFunction}"

# executes the original function
doComplicatedThings
The function doComplicatedThings is the production function which might have been loaded from an external script file via the source command. The definition of doComplicatedThings is then saved as a string into codeOfOriginalFunction.

The replacement function is just an empty stub. When executing the stubbed version of doComplicatedThings than as expected nothing happens. Calling eval restores the original function so that the second call of doComplicatedThings prints out the original message.

Dienstag, 12. Mai 2015

My Favorite Things - A One Time Downloader

For this blog it's a quiet year so far. I'm busy with work related stuff. Also, I'm collecting material for a book a colleague of mine and me are planning to write, more on that later.

Since Google Code is closing down I had to move an other old project of mine to github.com. It's a plain old CGI applications which allows small Do It Yourself (DIY) vinyl record labels to create small download vouchers for their releases. Being a vinyl collector myself five years back most of the vinyl records I bought came without any legal possibility to obtain the music in digital format as well.

Downloading the MP3 files for "A very good Record"
I wanted to make the world a little better here. Over the years the software has been used around the world. I was occasionally in touch with users. Some of them where so happy about the tool that they've send my a bunch of records they recently released.

This project was my first time getting out to the world with some software I developed. Back then I decided to use Perl as implementation language. For the reasons see here.

Nowadays I still would choose a dynamically typed language but I guess it would be Python then.

Sonntag, 7. Dezember 2014

Quick C Tricks - Simulate Keyword Arguments

Here is a little trick I found in the book Patterns in C written by Adam Tornhill. It will give you a syntax which looks like there are keyword arguments in C - but in fact there aren't:
...
int a, b, result;
result = sum(a = 2, b = 3);
...
This trick just moves the assignment of a and b insight the functions parameter section. However, this code is identical to:
...
int a = 2, b = 3, result;
result = sum(a,b);
...
So it is just some eye candy but are there any useful applications for that? Well, yes. This technique helped me to write some easy to read unit test code where I wanted to be explicit what the arguments of the function under test stand for.

Beside my unit tests I believe I won't use this syntactic sugar that often. If there is the need to pass in a couple of arguments to a function I would use this technique which relies on compound literals and designated initializers.

Dienstag, 25. November 2014

Learning Tests - Something you Shouldn't Smile About

I admit - at university I was one of the guys who would rarely attent a non-technical course. Just because I thought that this is the easy stuff - I don't need to waste my time with that.

Well, this aditute has changed.  Over the years I've learned the benefits of reflecting your work style, your personal progress and so on. Attempting to follow the principles of the agile manifesto is one of these findings.

The topic of this post are Learning tests, something a collegue of mine introduced to me and which I problably would have smiled about in university since it is not technical in itself - but it is a nice and easy way to invest your (usually limited) coding time carefully.

So what is it all about?Learning tests is about using your existing unit test framework to have a play with some new library function or class you haven't used before (or a long time ago). You are writing tests not against your own implementation but against the currently not so well understood new library function/ class you are planing to use in your own implementation later.

Learning tests are a kind of a step in between - the unit tests for a new feature you are planning to implement are there but you feel that you should examine the internal mechanics you will utilize for the implementation a little bit further (e.g. some complicated external library).
At this stage you slide in one ore more Learning tests which are writting using your every day unit testing frame work and hence live on with all the other unit tests. In your Learning tests you have a play with the unknown library until you understand how to employ it in your own work. You then move on to your actual implementation.

That's all about it. Nice, simple and definitly helpful.

Sonntag, 27. April 2014

Python doctest

Most of my exercises for SCIP come as a stub function where you have to implement the logic. To provide a way to self test your implementation, the stub functions contain also something called doctests.

doctests are a nice way to quickly add tests to your functions. This is what the Python manual says: The doctest module searches for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown.

Here is an example of one of my exercises:
from operator import add, sub
def a_plus_abs_b(a, b):
    """Return a+abs(b), but without calling abs.

    >>> a_plus_abs_b(2, 3)
    5
    >>> a_plus_abs_b(2, -3)
    5
    """
    if b < 0:
        op = sub
    else:
        op = add
    return op(a, b)
To run the two tests in the comment against the implementation I'm using the following command:
python3 -m doctest -v file.py

Donnerstag, 28. November 2013

I've learned the hard way - TDD in C, 2nd round

A while ago I wrote about how to insert "spy" code in C. That is the process of overwriting an existing function definition at link time with an own implementation to basically write unit tests.

After practicing for a while I have to admit that this approach works but is unpredictable and tricky to set it up. Unpredictable since I found it hard to guess in which exact order the linker is resolving the needed symbols (in this case function symbols), tricky when you're trying to get legacy code with many, sometimes depending libraries under test (which I currently do).

After some recent frustration I gave the search engine an other go and ended up with my new favorite toy: the --wrap option of ld, the GNU linker.

Here is what the man page of ld tells about it:
--wrap=symbol
    Use a wrapper function for symbol.  Any undefined reference 
    to symbol will be resolved to "__wrap_symbol".  Any undefined
    reference to "__real_symbol" will be resolved to symbol.

    This can be used to provide a wrapper for a system function.  
    The wrapper function should be called "__wrap_symbol".  
    If it wishes to call the system function, it should 
    call "__real_symbol".

    Here is a trivial example:

            void *
            __wrap_malloc (size_t c)
            {
              printf ("malloc called with %zu\n", c);
              return __real_malloc (c);
            }

    If you link other code with this file using --wrap malloc, 
    then all calls to "malloc" will call the function "__wrap_malloc" 
    instead.  The call to "__real_malloc" in "__wrap_malloc" will call 
    the real "malloc" function.

    You may wish to provide a "__real_malloc" function as well, so that 
    links without the --wrap option will succeed.  If you do this, you 
    should not put the definition of "__real_malloc" in the same file 
    as "__wrap_malloc"; if you do, the assembler may resolve the call 
    before the linker has a chance to wrap it to "malloc".

This functionality can of course also be used to overwrite some function which stands in our way for unit testing which I'm going to show you now.

I'm using the same test source as last time. Here are our example files:
// file fav_music.h
void tellFavoriteMusic( void );


// file program.c
#include "fav_music.h"

int main() {
   tellFavoriteMusic(); 
}


// file fav_music_indie.c
#include <stdio.h>

void tellFavoriteMusic() {
    printf("I like Indie!!!\n");
}


// file fav_music_soul.c
#include <stdio.h>

void __wrap_tellFavoriteMusic() {
    printf("I like Soul!!!\n");
}

Notice that the name of function tellFavoriteMusic in fav_music_soul.c has been changed to __wrap_tellFavoriteMusic.

When the three files are compiled and linked together (for clarity I keep this as two separate commands although gcc could do it in one go) as expected tellFavoriteMusic in fav_music_indie.c is executed by main():
$ gcc -c program.c fav_music_indie.c fav_music_soul.c
$ ls *.o
fav_music_indie.o  fav_music_soul.o  program.o
$ gcc -o program.out fav_music_indie.o fav_music_soul.o program.o
$./program.out
I like Indie!!!
This is something we actually expected: all function names are unique and the one specified is executed.

Now let the magic happen and tell the linker that it should replace a function call to tellFavoriteMusic with a call to __wrap_tellFavoriteMusic:
$ gcc -Wl,--wrap=tellFavoriteMusic -o program.out fav_music_indie.o fav_music_soul.o program.o
$ ./program.out
I like Soul!!!
program.c still calls tellFavoriteMusic, but this time its replacement __wrap_tellFavoriteMusic is called.

A note to the parameters syntax -Wl,--wrap=tellFavoriteMusic. Since we're calling the linker ld via the GNU compiler we need to tell gcc to pass the --wrap parameter through to the linker. Here is what the gcc manual says:
-Wl,option
    Pass option as an option to the linker.  If option contains 
    commas, it is split into multiple options at the commas.  
    You can use this syntax to pass an argument to the option.  
    For example, -Wl,-Map,output.map passes -Map output.map 
    to the linker.  When using the GNU linker, you can also 
    get the same effect with -Wl,-Map=output.map.
Although it looks similar -Wl is different to the common warning switch -W...

Other approaches
While browsing the Internet I stumbled  across some other approaches for  replacing a function with an other one at link time. My favorite ones use the tool objcopy which is part of the binutils and is used to change object files (the result of a compiler run).

Mark function to overwrite as "weak"
Here the idea is to use the ----weaken-symbol parameter of objcopy on the object file of the original function. By marking the function to replace as weak the linker should prefer a second function implementation with the same name which hasn't got that flag. The multiple definition error should not appear.

Temporary remove the original function definition from the object file
With this approach you generate a temporary object file which does not contain the conflicting original implementation of the function. I assume objcopy and/ or objdump could do that.

I haven't tried the last two ideas myself since I'm happy with the --wrap parameter solution. However, some of you might find those thoughts helpful as a starting point of own tests.

Montag, 16. September 2013

Inserting "Spy" Code in Plain C With Link Time Substitution

I'm currently working my way through the very recommendable book Test Driven Development For Embedded C. Although I'm not applying the techniques in an embedded environment, most of the book is still valuable for general C programing.

While test driving coding one has often to spy on objects. Meaning, to call a method or function of a library which is usually not under our control but to verify the result of the function/ method call.  
An example would be a logging function provided by a third party library. You can't change the library but you still are interested in the result of your code ( the code to test) calling this logging function/ method with a certain string.

In Java this problem is resolved by employing the dependency inversion principle. The third party logging library is not directly used inside the code. Instead an interface is defined offering an own logging function.
Now two objects are implementing this interface. The first one is the  production object which is in our example probably just a wrapper for a call to the original logging library.
The second object is a spy object (officially called test double) which implements the same interface but offering additional methods like e.g. getLastLoggingMessage.
The original application code is changed to only work with an object of the type of the new interface - not the logging class directly.  With the additional details provided by the test object you can than continue writing your test logic. This methodology is called dependency injection

Interestingly enough, this technique is also possible in a pure C environment. To understand the whole approach I might write an other post in the future. Here, I want to demonstrate how the technical part of dependency injection could be done. In my opinion the most convenient way to achieve this in C is a method called link time substitution.

The idea is to let the compiler (well, the linker part of the compiler) pick your spy implementation instead of the original implementation.

But maybe one step backwards. Compiling C files takes place in two stages: 1) compiling (roughly transforming the C code to an object file) and 2) linking (copying the object code of the functions needed into the final binary). Link time substitution is happening in the second stage - at the linker level. But it is in my opinion not totally obvious how this is precisely working so let's have a little example here.
// program.c

#include "fav_music.h"

int main() {
   tellFavoriteMusic(); 
}
This is our main program which calls the function tellFavoriteMusic. As it can be seen this function is not implemented in the same file but is provided somewhere else. To tell the compiler about tellFavoriteMusic the header file fav_music.h is included.
// fav_music.h

void tellFavoriteMusic( void );
The header file contains only the functions signature. Now there are two ways interpreting what is happening here.

A C programmer would say, that the content of fav_music.h is simple inserted before anything happens in program.c. Since now the declaration of the function is inside the file before it's invocation in main, the compiler (first stage) is happy with the setup. This is called a forward declaration.
progam.c doesn't need to see the actual implementation of tellFavoriteMusic at this level. It is later up to the linker to figure that out.

A Java porgrammer would probably say that program.c is implementing the fav_music.h interface not knowing about it's exact implementation. Since the Test Driven Development principles are coming from an object oriented background (thinking of the good old Smalltalk days) I prefer in this context the latter, rather high level explanation.

As next step we create two implementations of tellFavoriteMusic in two separate files which will be eventually compiled into two distinct object files:
//fav_music_indie.c
 
#include <stdio.h>

void tellFavoriteMusic() {
    printf("I like Indie!!!\n");
}
//fav_music_soul.c
 
#include <stdio.h>

void tellFavoriteMusic() {
    printf("I like Soul!!!\n");
}
Generating the object files  for the three source files is straight forward:
$ gcc -c program.c fav_music_indie.c fav_music_soul.c 
$ ls *.o
fav_music_indie.o  fav_music_soul.o  program.o
So lets recap what we've done. We transformed (compiled) our three C files into object files. The compiler (first stage) did no bother with the double implementation of tellFavoriteMusic since at stage one it is only focused on the current source file.

Lets step to stage two - linking. program.o currently contains a reference which says "I need an implementation of tellFavoriteMusic to execute." It is the task of the linker to find this implementation in the available object and library files.
So lets start with the obvious approach and see what is happening:
$ gcc -o program.out fav_music_indie.o fav_music_soul.o program.o 
fav_music_soul.o: In function `tellFavoriteMusic':
fav_music_soul.c:(.text+0x0): multiple definition of `tellFavoriteMusic'
fav_music_indie.o:fav_music_indie.c:(.text+0x0): first defined here
collect2: ld gab 1 als Ende-Status zurück
Not surprisingly the linker is complaining about the double implementation of tellFavoriteMusic. The situation does not change by moving the object files into different positions on the command line.

To resolve this issue, we need to look at how the linker is proceeding. First of all, file order counts! So in the above example first fav_music_indie.o, then fav_music_soul.o and then program.o are processed. Secondly, a double implementation of a function in object files (as in our example) always gives an error since the linker does not know which one to choose.

But there is a trick: If one function is stuffed into a static library (.a file) the linker will first pick the implementation from the object file and will than fall back to the implementation offered inside the library - provided that still the object file stands first on the command line.

We have a little play with that and generate an archive next:
$ ar cr fav_music_soul_lib.a fav_music_soul.o
$ ls *.a
fav_music_soul_lib.a
We have now a library fav_music_soul_lib.a which contains our object file fav_music_soul.o and therefore provides an implementation of the function tellFavoriteMusic.
$ gcc -o program.out program.o fav_music_soul_lib.a 
$ ./program.out
I like Soul!!!
The linker could puzzle everything together and provided us we the expected result. Let's get prepared for the last step.
Again,  we offer the linker the second implementation of tellFavoriteMusic but this time we place our object files first and have the library as the last argument:
$ gcc -o program.out fav_music_indie.o program.o fav_music_soul_lib.a
$ ./program.out 
I like Indie!!!
Now it has done the trick: The licker first picked the implementation of tellFavoriteMusic from the object file. By the time the linker hit the archive fav_music_soul_lib.a the dependency to tellFavoriteMusic was already resolved so it ignored the implementation offered there.

Coming back to the beginning of this post, with this little trick dependency injection is also working quite nicely with plain C.

Sonntag, 8. September 2013

Fazit "Software Sanierung" von Sebastian Kübeck - Teil 1c - Refactoring

Die umfangreiche Einführung des Buches "Software Sanierung" von Sebastian Kübeck muss sich natürlich auch dem Thema "Refactoring" widmen. Da ich mich in der Vergangenheit bereits intensiver mit Testgetriebener Entwicklung (TDD) beschäftigt habe, gab es in diesem Abschnitt kaum neues.

Eine Übersicht der behandelten Refactoring:
  • Umbenennen der Bezeichner (sprechende Namen für Variablen, Konstanten, Klassen...)
  • Methoden aus größerem Code-Block extrahieren
  • Methode auflösen, das Gegenteil von "extrahieren", Logik (nach Umorganisation) wieder in Ursprungsmethode zurück verschieben
  • Methode hochziehen - Verlangerung einer Methode von der Kinds- in die Elternklasse. Sinnvoll, wenn mehrere Kindsklassen die gleiche Methode nutzen.
  • Interface extrahieren - Erstellen eines neuen Interfaces und darauffolgend Ableiten der bestehenden Klasse von diesem Interface. Optional können auch Methoden der Klasse ins Interface wandern. Viele der Beispiele Im Buch beruhen auf dem Abhängigkeits-Inversions-Prinzip welches fordert, dass Klassen möglichst nicht von anderen Klassen sondern nur von deren Interfaces abhängig sein sollen. Dieses Refactoring spielt genau in dieser Liga.
  • Klasse extrahieren - aus einer Klasse eine Eltern-Klasse ableiten die optional auch Methoden  Implementierungen aus der Ursprungsklasse mitnehmen kann - ganz im Gegensatz zu Interfaces wo nur Deklarationen übernommen werden können. Letztendlich sind die beiden letzten Refactorings Alltag im Objekt-Orientierten Entwicklungsgeschäft.
Eine Gute Idee: Skizzieren von Refactorings
Da nicht immer am Anfang klar ist, was das günstigste Vorgehen ist, schlägt der Autor ein "Skizzieren" des  anstehenden Refaktorings vor: Außerhalb der Versionskontrolle können Source-Files umbenannt, umkopiert, neuangelegt werden. Es soll nicht kompilierfähiger Code herauskommen sondern eher eine Idee in welche Richtung das Refactoring getrieben werden soll.

Freitag, 23. August 2013

Fazit "Software Sanierung" von Sebastian Kübeck - Teil 1b, Entwurfsmuster

Der erste Teil des Buches "Software Sanierung" von Sebastian Kübeck beinhaltet eine umfangreiche Auswahl von Gang-Of-Four Entwurfsmustern inklusive guter Code-Beispiele. Die gewählten Patterns sind nach Ansicht des Autors "für das Sanieren" sinnvoll.

Nachdem ich auf meiner letzten Reise (nicht Urlaub) das Orginal-Gang-Of-Four Buch brav neben dem Bett liegen hatte, in der Hoffnung, dass sich Erkenntnisse daraus automatisch im Schlaf in Richtung meines Gehirns bewegen, habe ich hier die Chance ergriffen und die vorgestellten Muster im klassichen Schulprinzip durchgearbeitet. Dies sind meine Hefter-Notizen:

Abstrakte Fabrik
Klassisch wird ein Objekt mit "new" erzeugt. Eine Abstrakte Fabrik ist eine Klasse oder Interface, die mindestens eine Methode hat, die ein Objekt erzeugt.

Schablonenmethode/ Template Method
Ein Algorithmus wird in einer abstrakten Klasse definiert, wobei die Implementierung von Algorithmus-Details an Unterklassen ausgelagert wird. Zusätzlich kann die abstrakte Klasse auch noch leere Zwischenschrittsmethoden haben, die in Unterklassen optional implemenitert werden können.

Wert-Objekt
"Ein Wertobjekt ist dadurch gekennzeichnet, dass seine Identität von den Werten bestimmt wird aus denen es aufgebaut ist." Es hat keine eigene Identität. Im Buch wird noch darauf verwiesen, dass ein Kennzeichen von Java-Wertobjekten die Implementierung der Methoden "hashCode" und "equals" sind durch die der Programmierer einen Mechanismus zum Inhalts-Vergleich anbietet.
Ein Wertobjekt ist nach seiner Erstellung nicht mehr veränderbar (immutable).

Das Wert-Objekt ist nicht zu verwechseln mit dem auch in Javascript sehr populären "Data Transfer Objekt".

Null-Objekt
"Provide an object as a surrogate for the lack of an object of a given type. The Null Object Pattern provides intelligent do nothing behavior, hiding the details from its collaborators."
Im Buch wird ein Beispiel angeführt wo, abhängig von einem Konstruktor-Parameter, eine Ausgabe auf OutputStream-Objekt erfolgen soll - oder, wenn der Parameter "null" ist, anstelle dessen die Ausgabe über die Null-Objekt Implementierung von OutputStream.

Stellvertreter (Proxy)
Ein Objekt welches seine Methodenaufrufe an sein Orginal-Objekt weiterleitet. Stellvertreter und Orginal implementieren das gleiche Interface. Zusätzlich erfolgen aber noch weiter Aktionen im Stellvertreter die im Orginal nicht erfolgen.
Beispiel ist das Anklemmen von Logging. Dem Orginal-Objekt fehlt das Logging, der Stellvertreter besitzt die gleiche Signatur wie das Orginal, leitet die Methodenaufrufe auch an das Orginal weiter - aber loggt noch zusätzlich.

Adapter
Wie "Stellvertreter" allerdings underscheiden sich Methodensignatur von Orginal und Adapter Objekt. Dient als Verbindungsstück zwischen zwei verschiedenen Interfaces.

Beobachter
Häufig genutzt für Event-Handling in einer GUI. Die Beispiele im Buch haben aber eine andere Ausrichtung: Einem zu beobachtenten Objekt wird z.B. im Konstruktor ein Beobachter-Objekt übergeben. Dieser Beobachter könnte z.B. ein Logging implementieren.
An geeigneter Stelle werden dann im Orginal-Objekt die Beobachter-Methoden aufgerufen die dann z.B. zu einem Log-Eintrag führen.

Das Pattern ist praktisch für das testbar-machen von Log-Einträgen: Es gibt für das Beobachter-Objekt eine Produktiv-Implementierung, die einfach auf STDOUT schreibt und eine Test-Implementierung die sich zusätzlich das geloggte im Objekt merkt. Ein Unittest kann dann das "gemerkte" (z.B. eine Liste) vergleichen.
Dem zu beobachtenden Objekt (das mit der Fachlogik) wird dann der jeweilig passende Beobachter übergeben.

Fascade
Gang-of-Four Definition: "Biete eine einheitliche Schnittstelle zu einer Menge von Schnittstellen eines Subsystems. Die Fassadenklasse definiert eine abstrakte Schnittstelle, welche die Verwendung des Subsystems vereinfacht." (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: "Entwurfsmuster. Elemente wiederverwendbarer objektorientierter Software". Addison-Wesley. 1. Auflage 1996, S. 212)

Kommando
Kapsselung von Befehlen in ein Objekt. Im Buch wird ein Beispiel für einen einfachen Dateimanager gebracht. Die Hauptklasse (der FileManager) empfängt die Befehle als Arguments und ruft dann das entsprechende Kommando-Objekt auf. Die konkreten Aktionen zu einem Befehl sind im Objekt gekapselt.
Eine Erweiterung des Programmes ist einfach, es wird ein neues Kommando-Objekt angelegt (und getestet), dann wird der Hauptklasse noch das neue Objekt bekannt gegeben.

Strategie
Ein alter Bekannter im neuen Kleid, auch eine Anwendung des Abhängigkeits-Inversionsprinzips. Die Idee ist, Algorithmen die einer häufigen Änderung unterliegen via Interface zu implementieren.  und damit einfacher ausstauschbar zu machen.

Das Buch führt als Beispiel einen einfachen Passwort-Authenticator an. Die Passwort-Policy ist veränderlichen organisatorischen Regeln ausgesetzt - aus diesem Grund wird sie mittels Strategy-Pattern implementiert.
Konkret gibt es ein "PasswordPolicy" Interface welches nur die Methode "isSecure()" beinhaltet. Da die Frage "isSecure()" organisatorischen Änderungen unterworfen ist, wird die konkrete Implementierung von "PasswordPolicy" regelmäßig angepasst in dem eine neue Klasse geschrieben wird.
Die Authenticator-Hauptklasse bekommt im Konstruktor nur die jeweilig aktuelle Implementierung der Policy übergeben.

Mittwoch, 21. August 2013

Fazit "Software Sanierung" von Sebastian Kübeck - Teil 1a, Einführung und Design Prinzipien

Ohne konkrete Zahlen herauszukramen, stelle ich hier die Behauptung auf, dass ein Software Entwickler in seinem Berufsleben viel mehr mit existierendem Code arbeitet als dass er regelmäßig die berühmte "Grüne Wiese" bestellt.
 Dem Pragmatismus der Pragmatic Programmers folgend, versuche ich aus diesem Fakt eine Tugend zu machen, mit dem Ziel "Master Of Legacy Code" zu werden. Und ganz ehrlich, sich einen fremden, alten, nicht Unit-getesteten Code zu eigen machen - das ist doch im Grunde herausfordernder als immer nur ein neues Town House auf die planierte Fläche zu setzen.

Dieser Logik folgend stürzte ich mich neulich enthusiastisch auf das Buch "Software Sanierung" von Sebastian Kübeck, erschienen  2009 im mitp-Verlag. Um es vorweg zu nehmen: hiermit gestehe ich, dass in der Vergangenheit meine Unit-Tests häufig eher externe Tests waren.

Der erste Teil des Buches ist ein Crash-Kurs im benötigten Handwerkszeug:

  • Objektorientierung
  • Tests inklusive Abgrenzung der verschiedenen Test-Arten
  • Wichtige Design Patterns
  • Refactoring Patterns
  • Fehlerbehandlung (Exceptions)

Da dieses Blog mein öffentliches Gehirn sein soll, hier die Liste der Dinge, die ich als wichtig in diesem ersten Teil empfand:

Natürliche versus Künstliche Komplexität
Die natürliche Komplexität beschreibt letztendlich die Grund-Komplexität des implementierten Fachprozesses. "Der kleinstmögliche Umfang an Informationen, die notwendig sind um [ eine Problemstellung ] vollständig zu beschreiben, definiert die natürliche Komplexität des Problems."


Die natürliche Komplexität lässt sich nur verringern in dem man Features aus einer Anwendung wieder ausbaut und sie damit wieder vereinfacht.

Nun zur künstlichen Komplexität: "...der Ballast .., der nötig ist um Programme unter den gegebenen Rahmenbedingungen und mit den Kenntnissen der Programmierer zu realisieren."

Die künstliche Komplexität kann man direkt verringern, zum Beispiel durch gutes Design, Hochsprachen oder die Verwendung von Bibliotheken anstelle von eigenen Implementierungen. Ein guter Entwickler erhöht also bei einer Programmerweiterung die künstliche Komplexität nur um das wirklich notwendige Minimum - so weit zumindest die Theorie.

Sanieren statt Wegreißen
In der Einleitung werden ein paar gute Argumente für eine Sanierung bestehender Software angeführt, besonders wichtig finde ich diesen hier (in eigenen Worten):
Häufig ist die Software selbst die einzig verbliebene, aktuelle Spezifikation des Fachprozesses. Wissensträger sind teilweise nicht mehr verfügbar, die existierende Dokumentation ist lückenhaft. Allein das Programm beinhaltet das gesammelte Wissen der letzten x Jahre/ Jahrzehnte.

UML Klassendiagramme sind zwar schick aber...
...spiegeln nicht die Interaktion der Objekte wieder. Außerdem entsteht kein Programm aus einer Klassen/ Objekt-Beschreibung. Das scheint wohl noch aus der Zeit zu kommen, wo man Klassendiagramm gemalt hat und sich dann den Code per Knopfdruck generiert hat. 
Viel näher am Software-Entwicklungsprozess sind die Interaktionsdiagramme die erst später in die UML aufgenommen wurden. Das Nützlichste aus meiner Sicht ist das Kommunikationsdiagramm - letztendlich eine Formalisierung der Kästchen mit Pfeilen die man sowieso gern zur Visualisierung verwendet.

Interface-Aufteilungsprinzip
"Interfaces sollten nur so viele Methoden haben, wie für die Ausführung einer Aufgabe unbedingt nötig sind. Können zusätzliche Methoden zur Verfügung gestellt werden, sollte man das Interface aufteilen."

Liskov Substituitions-Prinzip
"If it looks like a duck, quacks like a duck, but needs batteries – you probably have the wrong abstraction" (Link)

Das Web ist voll mit Erklärungen dieses Prinzips.Grundsätzlich soll jede Erweiterung einer Klasse die Elternklasse vollständig ersetzen.

"Der Nachteil der Verletzung des Liskov-Substitutionsprinzips liegt in der Erwartungshaltung an eine Erweiterung einer Klasse. Da man dank der Polymorphie unter Umständen nur it der Elternklasse arbeitet, ohne zu wissen, dass man es eigentlich mit einer Ableitung zu tun hat, ist es äußerst unangenehm, wenn sich diese Klasse ganz anders verhält als die Elternklasse."

Für mich wird dieses Prinzip durch ein Beispiel am besten deutlich. Robert Martin hat dies sinngemäß einmal so erklärt: Mathematisch ist ein "Quadrat" ein "Rechteck". Man ist also versucht auch eine Klasse "Quadrat" von einer Basisklasse "Rechteck" abzuleiten.
Die Methoden "setX()"und "setY()" machen bei einem Rechteck durchaus Sinn - beim einem Quadrat allerdings nicht wirklich. Hier setzt der Aufruf einer Methode alle vier Seiten. Die Abstraktion "ein Quadrat ist ein Rechteck" passt hier also unter objektorientierter Betrachtungsweise schlecht.

Abhängigkeits-Inversionsprinzip
Auf diesem Prinzip bauen fast alle Refactorings des Buches auf. Es sagt aus, "dass Klassen möglichst nicht von konkreten Implementierungen anderer Klassen, sondern von deren Interfaces abhängig sein sollen".
In der Praxis bietet es sich an eine starre Kopplung z.B. an die JDBC-Klassen durch ein eigenes Interface aufzulösen, Die Produktionsimplementierung des Interfaces ist letztendlich ein Wrapper (ja, ich weiss, es heißt "Delegation") um die JDBC-Klassen.
Die Testimplementierung nutzt das gleiche Interface, emuliert aber Aktionen wie "getLastName()" mittels Hashmap.

Wenn nun der Ursprungsklasse während der Laufzeit eine andere Datenbank-Klasse mittels z.B. "setDatabase()" Methode "injiziert" wird, spricht man von "Dependency Injection".

Änderungsvektoren (einer Klasse)
Im Laufe der Zeit wird häufig eine Klasse durch verschiedene Änderungswünsche (Anforderungen) in verschiedene Richtungen getrieben. Diese verschiedenen Richtungen nennt man Änderungsvektoren.

Single-Responsibility Principle
Eine Klasse sollte nur einen dieser Vektoren implementieren. Also z.B. sich nur um Datenbankaktionen kümmern und keine Berechnungen durchführen.

Das Gleiche gilt für Methoden: checkAndStoreData() wird besser zu "checkData()" und "storeData()". Dieses Prinzip ist universell und gilt genauso für C Dateien und Funktionen.