Freitag, 27. Juni 2014

Rebase, aber richtig

Eine Sache ist klar: Rebase ist Böse, wenn es sich im Branches handelt, die schon publiziert wurden. Aber ab einer bestimmten Anzahl von parallel entwickelten Features (von Entwicklern will ich garnicht erst reden) wird es in der History unübersichtlich, wenn alle Commits zu allen Features nebeneinander stehen.

Es ist aber durchaus nicht abwegig, seine eigenen Branches in der Entwicklung gelegentlich zu rebasen. Wenn man bis dahin mit feature branches und --no-ff merges gearbeitet hat, stellt man aber schnell fest, dass Git die "überflüssigen" Merge-Commits einfach wegbügelt. Inhaltlich mag das sinnvoll sein, schließlich trägt ein Change, der nichts ändert, nichts zum Code bei (alle Ängerungen sind vorher geschehen). Wenn man aber mit kleinteiligen Commits arbeitet, verliert man so den Zusammenhang zwischen den Commits: Was gehörte jetzt zu dieser Implementierung, und was nicht?

Glücklicherweise hat Git ein Feature, welches zumindest beim Rebase das Zusammenfassen der Merge-Commits vermeidet: Mit

git checkout myfeature
git rebase -p master

wird der branch myfeature auf master rebased, wobei die Branch-Struktur im myfeature-Branch erhalten bleibt.

Mit git rebase sind noch mehr praktische Dinge möglich (ich meine hiermit Dinge, die einzelne Commits lesbarer machen). Aber nie vergessen: Einmal publizierte Branches darf man nur noch umschreiben, wenn man mit dem Frust aller Entwickler leben kann, die dann aufwändig ihre Commits neu einsortieren müssen.

Also eigentlich nicht.

Dienstag, 25. März 2014

Klammeraffen

Wenn man ein wenig mit Python rumspielt, stolpert man früher oder später über all die lustigen Dinge, die man mit Funktionen anstellen kann. Über die Vorzüge von funktionaler Programmierung brauche ich mich nicht auslassen, das haben schon andere getan. Jedenfalls hat sich die Python-Gemeinde irgendwann entschieden (ich bin zu faul, den genauen PEP rauszusuchen), die aus anderen Sprachen bekannten @-Dekoratoren einzuführen. (Vielleicht haben sie die auch erfunden, die Historie spielt für diese Anwendung keine Rolle.) Jedenfalls sind diese Dekoratoren in Python sehr einfach:

def decorated(func):
    def wrapper(*args, **kvargs):
        return func(*args, **kvargs)
    return wrapper


@decorated
def my_func():
    """my funky function"""
    pass

Will man dem Dekorator noch Argumente mitgeben, wird es ein wenig umständlicher:

def decorated(msg):
    def decorator(func):
        def wrapper(*args, **kvargs):
            print msg
            return func(*args, **kvargs)
        return wrapper
    return decorator


@decorated("nice function")
def my_func():
    """my funky function"""
    pass

Ich gebe zu, beim ersten Mal lesen erschließt sich das nicht unbedingt gleich: Der Code des Dekorators wird bei der Deklaration der dekorierten Funktion ausgeführt und liefert als Ergebnis die Funktion, die dann später bei Bedarf ausgeführt wird. Mit anderen Worten: Die dekorierte Funktion wird unsichtbar, an ihre Stelle tritt das Ergebnis des Dekorator-Aufrufs. Ach, ich geb's auf, immer wenn ich versuche, das zu erklären, wird es nur verworrener -- es sollte ausreichen zu wissen, dass es dutzende gute und einfache Erklärungen für die Funktionsweise gibt. Dummerweise ist das so einfach, dass man immer geneigt ist zu glauben, da müsse noch irgendwas kompliziertes, magisches im Hintergrund geben. Gibt es nicht.

Oder vielleicht doch: Die dekorierte Funktion heißt jetzt für alle Welt wrapper, und ihr fehlen solche Dinge wie der Docstring, den wir mühsam auf my funct function gesetzt haben. Das kann man jetzt mühsam händisch nachrüsten, oder man verwendet wraps aus den functools. Was wohl die bessere Idee ist?

def haendisch(func)
    def wrapper(*args, **kvargs):
        return func(*args, **kvargs)
    doc = getattr(func, '__doc__', None)
    if doc:
        setattr(wrapper, '__doc__', doc)
    return wrapper


import functools
def bessere_idee(func):
    @functools.wraps(func)
    def wrapper(*args, **kvargs):
        return func(*args, **kvargs)
    return wrapper

Die Funktion bessere_idee sieht schon besser aus, wie ich finde. wraps kümmert sich rührend um Funktionsnamen und Docstring -- einfach immer verwenden, wenn man irgendwelche Funktionen wrapped. Und nicht vergessen: Immer wenn man eine wirklich coole Idee für einen Dekorator hat, der irgendwas wildes mit der Funktion, der Klasse oder dem Objekt anstellen kann -- je weniger wild und cool, desto besser.