Less Code and Less Bugs : with Functional Programming languages

With a FP language many problems can
be solved with elegantly less code and
less-code leads to less-bugs.

Functional Programming (FP) insider know that, because they have personally experienced it. This can be tweeted and blogged many times, but how can others believe this? How do you prove it?

That problem inspired me to do some investigation. We need facts. What I’ve found so far are two studies that show this from a data analysis side. There is some evidence that both is true, according to these newer papers from 2014, based on data analysis of source code and its history from Github and from Rosetta Code :

Functional languages have a smaller relationship to defects than other language classes where as procedural languages”
– [1] A Large Scale Study of Programming Languages and Code Quality in Github
Functional and scripting languages provide significantly more concise code than procedural and object-oriented languages.”
– [2] A Comparative Study of Programming Languages in Rosetta Code


In [2] the data analysis covers 8 widely used languages representing the major programming paradigms
- procedural: C and Go;
- object-oriented: C# and Java;
- functionalF#  and  Haskell;  
- scripting:  Python  and  Ruby


I think the “less code” property is underestimated, because the too easy Lines of Code (LOC) counting is ridiculed. Looking beyond the LOC of the code base to the more important depending consequences :

less code  ->  less to read (more often than write!) and understand   -> 
less to review ->  less to communicate ->  less to refactor -> 
less to test  ->  less bugs  -> 
less to maintain  ->  less cost!!!

Btw. less code -> more time to think -> better solution -> more fun!


References

Other related analysis about software quality metrics, especially circular dependencies can be found here:
- “Comparing F# and C# with dependency networks
- “Cycles and modularity in the wild – Comparing some real-world metrics of C# and F# projects”

[1] A Large Scale Study of Programming Languages and Code Quality in Github
Baishakhi Ray, Daryl Posnett, Vladimir Filkov, Premkumar T Devanbu, Department of Computer Science, University of California, Davis, CA, 95616, USA
2014 http://dl.acm.org/citation.cfm?id=2635922


[2] A Comparative Study of Programming Languages in Rosetta Code
Sebastian Nanz, Carlo A. Furia, Department of Computer Science, ETH Zurich, Switzerland
2014 http://arxiv.org/abs/1409.0252



Zuverlässige Software durch weniger bewegliche Teile

Jedes mechanische System wird einfacher und wartungsfreundlicher, je weniger bewegliche Teile es hat. Die Einfachheit macht es auch langlebiger und weniger Störungsanfällig.

In der Softwareentwicklung findet man auch “bewegliche Teile”, so werden Variablen definiert in C#, C++

1: 
int myVariable = 0; 

in F# mit mutable

1: 
let mutable myVariable = 0

Jetzt könnte man denken, das ist ja furchtbar, immer noch dieses mutable hinzuschreiben. Und genauso ist es, denn der Standart (default) ist ohne mutable

1: 
let myValue = 0

Das hat seine Bedeutung, es ist sicher aufgefallen dass im Beispiel die Benennung von Variable auf Value gewechselt hat. Der Grund ist, in F# gibt es Standart mässig nur Values. Will man Veränderliche, so ist man gezwungen mutable hinzuschreiben. Also die beweglichen Teile zu markieren, so wird mutable auch als Code-Smell, als nicht so fein duftender Code bezeichnet.

Unveränderlich (Immutable)

Was hat es nun mit den Values auf sich?

Values sind einmal zugewiesen nicht wieder veränderbar. In C++ und C# ist es genau umgekehrt, man kann variablen mit const markieren. Das wird kaum angewandt, weil zu aufwendig oder nur dann wenn z.B. Mathematische oder Physikalische Konstanten definiert werden.

Ein F# Value ist konstant.

Ja aber bitte, wie soll man nur mit Konstanten programmieren können? Die Frage ist gut, die Antwort wird sie überraschen.

Angenommen sie wollen in eine string (.Net) myString = “aBc” das B durch ein kleines b ersetzen, wie machen sie das?

Nein, nicht so

1: 
myString[1] = 'b'

Sie könnten

1: 
myString.ToLower()

verwenden, was erhält man?

Ist nun myString = "abc" ?

Nein, man erhält einen neuen string “abc” als Rückgabewert von der Funktion ToLower().

Ah ja klar.

Es fällt auf, dass die string Instanz eigentlich eher ein Value ist, als eine Variable, also der string immutable ist, das Gegenteil von mutable.

Values sind immer zugewiesen, es gibt keine nicht initialisierten Values in F#.

Initialisierte Values (keine null Referenzen)

Es mag in C#/C++ nicht initialisierte Variablen oder Objekte geben.

1: 
2: 
object myObject;        // war früher in C/C++ nicht unbedingt null
object myObject = null; 

Bedeutet null nun, nicht initialisiert oder nicht zugewiesen oder nicht vorhanden oder kein Ergebnis oder Fehler ?

1: 
return null;

Da müssten jetzt die Alarmglocken losgehen, oder wie ist das denn genau in den von Ihnen angewandten Codierrichtlinien definiert?

Wie oft kommt dies im Code vor?

null

1: 
2: 
3: 
if x == null    // C++/C#
if x != null    // C++/C#
if x <> null    // VB

Nothing

1: 
2: 
If x Is Nothing Then // VB.NET
If IsNothing(x) Then // VB.NET

Nullable

Die Möglichkeit einen nicht-Referenz Type wie z.B. int auch null setzen zu können.

1: 
2: 
3: 
4: 
System.Nullable<int> x = 4;
int? x = 4;

int? a = null;

Der F# Option type ersetzt die null und das Nullable auf einsichtigere Weise mit static typing.

Die Bedeutungen von null und deren negative Konsequenzen

Meint null nun, nicht initialisiert oder nicht gültig?

Die Frage an C# Entwickler, Antwort oft: Beides!

In F# ist alles IMMER initialisiert, eine Option.None (ähnlich null) sagt, dass es keinen (gültigen) Wert hat.

Der Option Type als Rückgabe Parameter einer Funktion besagt, dass die Funktion auch None liefern kann. Somit bei static typing immer geprüft wird UND in Interfaces ist klar was optional ist!

Ob eine C# Methode null liefert oder nicht weiss man nicht explizit.

Also sollte man bei Verdacht immer prüfen? Um sogenanntes FailSafe Verhalten zu erzeugen?

Schön dass String in .Net ein immutable Type ist, aber leider leider kann er auch null sein. Dazu gibt es dann so nette Funktions Klassiker wie String.IsNullOrEmpty()

1: 
result = s == null || s == String.Empty; // String.IsNullOrEmpty()

All dies ist in F# nur dann ein Problem, bei Interop mit C# oder VB. Sonst ist die Welt in Ordnung.

Wie oft geht dies vergessen und führt so zu Fehlverhalten in der Software (Null-Refernece Exception)?

Was dazu führen kann (Fail-Save Programming), dass auch zuviele und unnötige null Tests gemacht werden und der Code fast ausschliesslich daraus besteht und ein Reviewer die Logik des Codes über weite Flächen erkunden muss.

In F# kann man darauf verzichten. Der Code ist Spezifikation und enthält nur das Wesentliche.

Das Problem ist ja, dass man nicht wissen kann, ob an dieser und jener Stelle eine null Prüfung gemacht werden muss oder nicht. Da kann man Code Annotations machen und externe Tools bemühen, die Code Dokumentation aufblähen, nur weil es keine Konvention oder Interface hat.

Der Option Type in F# stellt so ein Interface zu Verfügung. Das Interface kann der Compiler überprüfen. Man erkennt ob eine Funktion eine Option als Parameter verwendet und ob eine Option zurückgegeben wird. Es ist alles klar definiert. Keine Annahmen, die je nach Projekt oder Software Team in einer Konvention definiert wurde, z.B. in den Codierrichtlinien.

Dies ist nichts weiter als die Behandlung von Symtomen, anstatt die Ursache, die Null-Referenz zu bekämpfen. In F# ist keine null nötig und der F# Compiler kennt die “Codierrichtlinien” und prüft ob die Option korrekt ausgewertet wird.

So jetzt vergessen wir all diese Bauchschmerzen.

In F# gibt es nur initialisierte Values.

Somit braucht man die null nicht.

Und dies erübrigt auch die null Abfragen*.

So einfach und so praktisch!

*) Dies wohlbemerkt innerhalb der F# Welt. Bei Anwendung und Interfacing von nicht F# Modulen, muss man leider aus Sicherheitsgründen alles was rein kommt überprüfen.

Also dies ist nur ein Teil der wegfallenden beweglichen-Null-Teile, die nicht nur das Denken des Entwicklers vereinfacht auch der Code und somit die Software selbst.

Man kann es als Evolution der Programmiersprachen sehen

1: 
2: 
3: 
4: 
5: 
6: 
1965    Algol   null Referenz                                           Tony Hoare gilt als Erfinder der null Referenz
1972    C       Pointer (null)                                          Speicher Verwaltung mit malloc
1985    C++     nicht Initialisierte Objekte (null), Pointer (null)     Speicher Verwaltung mit malloc
2001    C#      nicht Initialisierte Objekte (null)                     Garbage-Collector verwaltet den Speicher
2002  VB.Net    nicht Initialisierte Objekte (null)                     Garbage-Collector verwaltet den Speicher
2002    F#      initialisierte Values, Static Typing                    Garbage-Collector verwaltet den Speicher

Ja F# gibt es seit 2002. Warum F# gut ein Jahrzehnt später populär wird liegt wohl daran, dass Microsoft F# erstmals in Visual Studio 2010 mit auslieferte, zuvor musste man es separat installieren, um es im Visual-Studio zu nutzen.

Static Typing

1: 
2: 
3: 
4: 
5: 
Dim Text As String  = "Hello world"     // VB
CString Text        = "Hello world";    // C++
String Text         = "Hello world";    // C#
var Text            = "Hello world";    // C#3 - Inferred type: String
let Text            = "Hello world"     // F#  - Inferred type: String  

Dass Text ein String sein muss, erkennt das F# Type-System automatisch ohne zu kompilieren! Der Tooltip unterm Mauszeiger zeigt den type. Dies verkürzt den Code, vorallem bei Funktions Deklerationen. Da werden von allen Parametern die Typen automatisch erkannt. Es ist also viel mächtiger als das C# var Konstrukt.

Dies vereinfacht das Code Refactoring, man muss die Type Spezifikation nicht “rumschleppen” und benötigt folge dessen kein extra Tool dazu (das auch noch auf die Compiler Version abgestimmt sein muss), weil es der Compiler selbst ist. Und es ist, dem Anschein zu Trotz, weil da ja keine Typen mehr stehen, strikt Type-Safe.

Die Vorteile von immutable sind

  • thread safety guarantee
  • besser testbar
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

——————–
type int = int32

Full name: Microsoft.FSharp.Core.int

——————–
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val mutable myVariable : int

Full name: ZuverlässigeSoftwaredurchwenigerbeweglicheTeile.myVariable
val myValue : int

Full name: ZuverlässigeSoftwaredurchwenigerbeweglicheTeile.myValue
namespace System
Multiple items
type Nullable =
  static member Compare<'T> : n1:Nullable<'T> * n2:Nullable<'T> -> int
  static member Equals<'T> : n1:Nullable<'T> * n2:Nullable<'T> -> bool
  static member GetUnderlyingType : nullableType:Type -> Type

Full name: System.Nullable

——————–
type Nullable<'T (requires default constructor and value type and 'T :> ValueType)> =
  struct
    new : value:'T -> Nullable<'T>
    member Equals : other:obj -> bool
    member GetHashCode : unit -> int
    member GetValueOrDefault : unit -> 'T + 1 overload
    member HasValue : bool
    member ToString : unit -> string
    member Value : 'T
  end

Full name: System.Nullable<_>

——————–
System.Nullable()
System.Nullable(value: 'T) : unit
module String

from Microsoft.FSharp.Core
val Text : string

Full name: ZuverlässigeSoftwaredurchwenigerbeweglicheTeile.Text

Videos zum Thema Funktionale Programmierung (FP)

Hier sind einige neue interessante und zum anders Denken anregende Videos zum Thema Funktionale Programmierung (FP). Warum sich jeder Software Entwickler mit FP befassen muss!

Weckruf – rüttelt an dem wie man bisher Software entwickelt(e).

Titel: Functional Programming: What? Why? When? Aussage: The Failure of State Autor: Robert C. Martin Titel: Programming in Interesting Times Aussage: YOUR PROGRAMMING LANGUAGE IS GOING TO DIE Autor: Russ Olsen Titel: One Hacker Way, a Rational Alternative to Agile Aussage: With Scrum we talk too much about code, instead of writing code. Uses the F***-word Autor: Erik Meijer Titel: Type Systems – The Good, Bad and Ugly Aussage: Not relying on type safety is unethical (if you have an SLA) Autor: Paul Snively and Amanda Laucher

   

Zeigt die Möglichkeiten von F#

Titel: FSLAB: DOING DATA SCIENCE WITH F# Aussage: How to get knowledge from data? Autor: Tomas Petricek Title: MBrace: large-scale programming in F# Aussage: Cloud Computing is easy with F# Autor: Eirik Tsarpalis

Der Wert von F# – The Value of F# (FSharp)

Zu meinem Tweet, der aus einer simplen Handskizze bestand, ergab sich Interessantes Feedback (nebst Re-Tweets, Favorites und als Bild in F# Weekly #38, 2014) Value of FSharp F#

Zu meinem Tweet

Den Wert von etwas erkennt man am besten (und schmerzhaftesten) dann, wenn man es nicht mehr hat. Den Wert der eine Programmiersprache einem Softwareentwickler geben kann ist NICHT zu unterschätzen.
  • Bevor man F# anwendet erkennt man deren Nutzen anhand einiger Features die einem gefallen und sofort erkennbaren Nutzen versprechen.
  • Während man F# nutzt und anderst zu denken beginnt (das ist etwas positives) erkennt man den wahren Wert; und es ist mächtig, man findet immer Neues und ist fasziniert.
  • Wenn man F# nicht mehr nutzt (nutzen darf, wie auch immer) erkennt man erst richtig all die kleinen mächtigen Dinge die man hatte. Und ohne diese ist man weniger produktiv und Fehler anfälliger.

Feedback auf den Tweet

Man lese diese Blogs, um zu verstehen, wie es anderen ergangen ist. Und ich wünsche allen denen es so ergangen ist, dass sie zurück in die F# Welt kommen können. Ihr macht das! Viel Glück!  

Die Strukturierung von F# Quellcode

Eine neue Seite zum Thema der Strukturierung von F# Code ist dazugekommen. Es geht um: – warum F# Code so kompakt ist – wie einem die Type-Inferenz behilflich ist sich auf das wesentliche zu Konzentrieren - und wie man dadurch weniger Fehler macht weiterlesen…..

Der HeartBleed Software Fehler

Und wieder ist, heute am 8 April 2014, ein kapitaler Software Fehler publik geworden. Mich interessiert, wie es zu solchen Fehlern kommt, warum sie so lange nicht erkannt wurden, wie sie zu erkennen sind und am aller wichtigsten, wie man solche Fehler vermeiden kann. Hier kann man übrigens testen ob ein Server von HeartBleed betroffen ist http://filippo.io/Heartbleed/.

Analyse des HeartBleed Bugs

Die beste bisher aufgetauchte Beschreibung des Fehlers habe ich hier gefunden https://news.ycombinator.com/item?id=7549943 von drv und darin die interessanten Stellen fett markiert : TLS heartbeat consists of a request packet including a payload; the other side reads and sends a response containing the same payload (plus some other padding). In the code that handles TLS heartbeat requests, the payload size is read from the packet controlled by the attacker:
1: 
2: 
n2s(p, payload);
pl = p;
Here, p is a pointer to the request packet, and payload is the expected length of the payload (read as a 16-bit short integer: this is the origin of the 64K limit per request). pl is the pointer to the actual payload in the request packet. Then the response packet is constructed:
1: 
2: 
3: 
4: 
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
The payload length is stored into the destination packet, and then the payload is copied from the source packet pl to the destination packet bp. The bug is that the payload length is never actually checked against the size of the request packet. Therefore, the memcpy() can read arbitrary data beyond the storage location of the request by sending an arbitrary payload length (up to 64K) and an undersized payload. I find it hard to believe that the OpenSSL code does not have any better abstraction for handling streams of bytes; if the packets were represented as a (pointer, length) pair with simple wrapper functions to copy from one stream to another, this bug could have been avoided. C makes this sort of bug easy to write, but careful API design would make it much harder to do by accident.

Vorkehrung

Die genannte Vorkehrung wäre eine einfache Datenstruktur, ein Tupel (pointer, length), um die zusammengehörenden Daten zu vereinen, damit es nicht zu Verwechslungen kommen kann. In F# Syntax sähe das so aus
1: 
let buffer = (pointer, length)
oder man erzeugt einen Record
1: 
type Buffer = { pointer : int64;  length : int64 }
Ja aber wenn wir schon bei Verwechslungen sind, wie vermeidet man hier dass die length und pointer nicht aus versehen vertauscht zugewiesen werden?
1: 
2: 
let buffer = { l; p }   // Vertauscht! Compiler reklamiert nicht :-(
let buffer = { p; l }   // pointer, length  
Eine Verwechslung der Parameter kann der Compiler so nicht erkennen, es sind ja in diesem Beispiel beides long ! Was kann man dagegen tun? F# hat dazu die sogenannten Units of Measures, also Maßeinheiten, wie Kilogramm und Meter, die ganzen SI-Einheiten sind schon vordefiniert, das nur am Rande, denn wir brauchen hier was anderes. Wie wäre es mit den Einheiten Pointer und Length ? Diese kann man sich wie folgt definieren
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
// Units of Measures
type [<Measure>] Pointer
type [<Measure>] Length

// Record Type
type Buffer = 
    { pointer : int64<Pointer>
      length  : int64<Length> }

let p = 123456L<Pointer>
let l =    512L<Length>

let buf  = { pointer = p;  length = l }
//    val buf : Buffer = {pointer = 123456L;
//                        length = 512L;}

// Verwechslung bei der Zuweisung
let buf  = { pointer = l;  length = p }
//    error FS0001: Type mismatch. Expecting a
//        int64<Pointer>    
//    but given a
//        int64<Length>    
//    The unit of measure 'Pointer' does not match the unit of measure 'Length'
Als Kommentar sind die Compiler Meldungen angehängt, dort ist schön zu sehen wie F# reklamiert, dass dem Pointer keine Length zugewiesen werden kann.

Noch mehr Bugs?

Interessant ist auch, dass genau da wo der HeartBleed Bug die Ursache hat, schon einmal etwas gefixt wurde am 27 Feb 2012,
“This patch fixes two padding related bugs for the Heartbeat Response messages. For DTLS, the wrong pointer was used, which may overwrite the payload with the random padding. For TLS, there was no random padding at all.” http://marc.info/?l=openssl-dev&m=133035562503173
Das gibt der alten ungeschriebenen Regel wiedereinmal mehr recht:
Da wo ein Bug ist, sind noch mehr.

Fazit

Wichtig war es mir hier anhand aktueller echter Probleme aufzuzeigen, wie man dies elegant und besser lösen kann. Und mit dem Potential das F# mitbringt ist es bestens geeignet um komplexe Probleme einfach und korrekt zu lösen. Man denke dabei auch daran, wie viele Unit-Tests man einsparen kann, wenn der Compiler so strikt prüfen kann.
Multiple items val int64 : value:’T -> int64 (requires member op_Explicit)Full name: Microsoft.FSharp.Core.Operators.int64——————– type int64 = System.Int64Full name: Microsoft.FSharp.Core.int64——————– type int64<‘Measure> = int64 Full name: Microsoft.FSharp.Core.int64<_>
Multiple items type MeasureAttribute = inherit Attribute new : unit -> MeasureAttributeFull name: Microsoft.FSharp.Core.MeasureAttribute——————– new : unit -> MeasureAttribute