An F# rewrite of a fully refactored C# Clean Code example

Today I stumbled over a C# clean code refactoring example, where I could not resist to do a full rewrite in F# during my break.

The reason for doing this was mainly to present a comparison how does a piece of deeply refactored C# code can look in F#, to motivate others to learn F#, because it makes things (software: thinking, design, coding, review and tests) much simpler and leads to robust solutions.

And the other reason was to show the nice F# syntax coloring that is possible with the Visual F# Power Tools (VFPT) that is an excellent and must have add-on in VS-2015. This screenshot is taken with my customized coloring scheme.

The F# code
F#-Rewrite-of-refactored-c#

The C# code

“C# BAD PRACTICES: Learn how to make a good code by bad example” http://www.codeproject.com/Articles/1083348/Csharp-BAD-PRACTICES-Learn-how-to-make-a-good-code

If you like to have deep detailed F# training please see our F# 2-day course information (German language).

Ok that’s it, I hope you got some new ideas by comparing the F# and C# code.


Update
??.03.2016 : Some more C# refactoring of this example by Ralf Westphahl here http://ralfw.de/2016/03/dont-let-cleaning-up-go-overboard/

Update 26.01.2017 : More C# refactoring by Pete Smith here https://gist.github.com/beyond-code-github/8711794c4d516cb6941d47274884b248

Update 26.01.2017 : Even more C# refactoring of this example by David Arno with C# 7 and Succinc<T> here http://www.davidarno.org/2017/01/26/using-c-7-and-succinct-to-give-f-a-run-for-its-money/

Update 27.01.2017 : An explanation of the F# code by Richard Dalton  http://www.devjoy.com/2017/01/reading-f/

Update 27.01.2017 : Much more C# refactoring by Kenneth Truyers https://www.kenneth-truyers.net/2017/01/27/refactoring-taken-too-far/

Update 28.01.2017 : An similar F# refactoring , with a merged discout logic (do the domain owner like that?) by Jon Harrop https://gist.github.com/jdh30/b01279a6be91467c5887b72a7c2f303e

Update ??.??.2018 : await C# 8 refactoring // ToDo: check if this F# code was also possible with F# 1.9 (2007)





Die Strukturierung von F# Quellcode

F# Code ist sehr kompakt, weil unnötiges und redundantes entfällt. Kurz gesagt, keine ; { } und keine Typen Deklaration. So, nun im Detail alles der Reihe nach:

Keine ;

Das Ende einer Code-Zeile mit ; abzuschliessen ist in F# nicht nötig. Man kann, sieht dann aber aus wie umgewandelter C# oder C++ Code. ; werden verwendet um Aufzählungen von Records, Listen, Arrays etc. zu trennen. Wobei man auch hier, bei nur einem Element pro Zeile den ; nicht zu schreiben braucht.
1: 
2: 
3: 
4: 
5: 
6: 
7: 
let xs = [ 1; 2; 3 ] 
let xs = 
       [ 
        1
        2
        3
       ]

Keine {}

F# Code kommt ohne Code Blöcke aus die in geschweifte Klammern { } gefasst sind. Die Struktur ergibt sich aus der sauberen Einrückungen mittels Tab oder Space (white-space aware code). Die meisten Codierrichtlinen geben ja heutzutage genau eine solche Code Formatierung vor, zusätzlich zu den {} Klammern was redundant ist. Also wieso nicht die Klammer weglassen. Mag sein, dass das für manchen am Anfang etwas komisch aussieht, aber man gewöhnt sich so schnell daran. Kommen in F# doch { } vor, dann handelt sich um einen Record Type oder Computation Expressions

Wie geht denn das ohne { } Code-Blocks ?

Bei gut gestaltetem Code achtet man darauf, dass die Code-Blocks korrekt eingerückt sind. Also z.B.
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// C#
void Bar()
{
    bool b = Foo();
    if b 
    {
        FooTrue1();
        FooTrue2();
    }
    else
    {
        FooFalse();
    }
}
Das selbe in F# ist durch weglassen von ; und { } zu erreichen, bei Einhaltung der korrekten Einrückungen.
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// F#   
let bar() =
    let b = foo()
    if b then
        FooTrue1()  // Die Einrückung gilt für mehrere Code Zeilen. 
        FooTrue2()       
    else
        FooFalse()
 

Keine Typen Deklaration

Es ist sicher schon aufgefallen, dass bei Funktionen keine Typen angegeben wurden.

Fehlt da dann nicht etwas, wenn im Code die Typen fehlen?

Wie geht das ohne Typen zu deklarieren? Die sogenannte Type-Inferenz findet selbst heraus um welchen Type es sich handeln muss. Man stelle sich vor, man ruft eine API-Funktion auf die data als Parameter entgegennimmt. Diese Funktion verarbeitet int array, somit ist data vom Type int array, also muss man data nicht extra deklarieren. In Visual-Studio oder auch im Blog Code hier, zeigt ein Tooltip über dem jeweiligen Wert oder Funktion dessen Type an, wenn man überprüfen will was es denn nun ist.
Der Code wird dadurch schlanker zu lesen und auch zu schreiben. Wobei Code ja mehr gelesen (Code Review) als geschrieben wird, ist das sehr nützlich, denn Kompakter Code ist schneller zu verstehen!
Kompakter Code, nicht im Sinne von alles auf eine Zeile quetschen, ist schneller zu verstehen, als wenn man Seitenweise lesen muss und sich immer die Typen Deklaration mitdenken muss. Überlassen wir die Typen dem “Compiler”, der prüft diese, und zwar schon vor dem Compilieren, die TypeInference die das erledigt läuft im Editor mit. Passt etwas nicht, wird die Stelle mit der bekannten rote Wellenlinie unterstrichen. Im Tooltip dazu wird erklärt was da nicht passt und was das Problem lösen kann.
Wir kümmern uns darum WAS gemacht wird. Ob alles passt, darum kümmert sich die TypeInference. Es ermöglicht ein abstrakteres Denken, wenn man sich nicht um die Typen Details kümmern muss und spart Entwicklungszeit.
Dies führt zum oft erwähnten positiven Effekt, den man dabei erlebt, dass wenn alles passt, der Code auch gleich erfolgreich kompiliert und meist auch prompt funktioniert. Diese Erfolge motivieren und man will TypeInference nicht mehr missen.

Generische Typen

Selber denkt man eher generisch, also mit generischen Datentypen. Es ist in F# so:
Wenn die Type-Inferenz nicht herausfindet, was es für ein Type sein muss, dann ist der Code automatisch generisch!
Super, man muss also dafür nicht extra überall <T> hinschreiben, das ist dann automatisch so!
1: 
2: 
3: 
4: 
5: 
// C#
void Swap<T>(List<T> list1, List<T> list2)
{
    // code to swap items
}
Und das equivalente generische swap in F#:
1: 
2: 
// F#
let swap (x,y) = (y,x)  // val swap : x:'a * y:'b -> 'b * 'a
und das funktioniert dann auch z.B. mit int List
1: 
2: 
3: 
4: 
let xs = [1; 3]
let ys = [4; 5; 6]
swap (xs,ys)
//val it : int list * int list = ([4; 5], [1; 3])
Auch das Refactoring wird dadurch vereinfacht, man muss die Typen nicht mitschleppen, also lesen und schreiben, umkopieren.
Weil es so einfach ist, braucht man dazu auch keine Refactoringtools! Viele glauben es zu beginn nicht, aber es ist so! Ihr werdet es merken ;-)
Etwas krass ausgedrückt werden Refactoringtool vielfach dazu verwendet, die “nebensächlichen” Teile des Codes zu erzeugen, die man nicht schreiben will. Steht der Code und man muss umbauen, dann müssen all die “nebensächlichen” Teile syntaktisch korrekt verpflanzt werden (Code-Plumbing wie man es auch nennt). Man kann es so sehen:
Die F# Syntax ist so ausgelegt, dass vieles nebensächliche gar nicht vorkommt und so das WAS gemacht wird sofort und klar ersichtlich ist.
Wie z.B.
  • keine “;” am Ende der Codezeile
  • keine { } Code-Blocks
  • keine Typen Deklaration
  • keine Generische Typen Deklaration
Positives Fazit:
Wer weniger schreibt, macht weniger Fehler. Wer weniger lesen muss, findet Fehler eher. Wer weniger lesen muss, arbeitet sich schneller ein. Weniger Code, weniger Bugs. Weniger Code, weniger Code Review. Weniger Code, Entwickler schneller eingearbeitet.
Ja und es geht nicht hauptsächlich darum : “Wer weniger schreibt, ist schneller fertig.” … es ist nett wenn das auch noch so ist ;-)
val xs : int listFull name: StrukturierungvonFQuellcode.xs
type bool = System.BooleanFull name: Microsoft.FSharp.Core.bool
Multiple items module Listfrom Microsoft.FSharp.Collections——————– type List<‘T> = | ( [] ) | ( :: ) of Head: ‘T * Tail: ‘T list interface IEnumerable interface IEnumerable<‘T> member Head : ‘T member IsEmpty : bool member Item : index:int -> ‘T with get member Length : int member Tail : ‘T list static member Cons : head:’T * tail:’T list -> ‘T list static member Empty : ‘T listFull name: Microsoft.FSharp.Collections.List<_>