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

Weniger Softwarefehler durch Klarheit – null ist Vergangenheit in F#

Die null ist leider die Quelle vieler Laufzeitfehler.

Die null-en befüllen die Code-Basis und machen sie weniger gut lesbar und wartbar.

Ein typisches Codefragment in C#:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// C#
if (x != null) 
{
    return UseALanguageWithoutNull( x );
}
else
{
    return null;
}

Wie es ohne null aussieht

1: 
2: 
// F#
UseALanguageWithoutNull x

Null vs. Option.None?

Wir wissen dass in F# alle Werte initialisiert sind und niemals mit null. Mit der Ausnahme, dass von einer .Net Komponente die nicht in F# geschrieben ist, ein Rückgabewert oder ein Out Parameter als null daherkommt.

Was ist nun, wenn man den Zustand von nicht vorhanden in F# abbilden will?

Dazu dient der Option Type. Dieser kann None oder Some 'T sein. Wobei ‘T ein beliebiger Type sein kann, also z.B. ein int, ein String oder ein Array.

Jetzt könnte man ja sagen es ist genau das selbe?

1: 
2: 
null    vs.     None
obj     vs.     Some 'T

Die Unterschiede sind:

  • Es ist nicht umständlicher einen Wert in einen Option type zu packen. Auch wenn es im ersten Augenblick so aussehen mag. Der Nutzen überwiegt!
  • Der Type ‘T von Some ist bekannt, und auch von None, also welcher Type der keinen Wert hat. Dies ermöglicht die exakte statische Type Überprüfung durch den Compiler, was zu sicherem Code führt. Es wird also nicht zur Laufzeit implizit ge-castet.
  • Elimination der Mehrfachbedeutung von null. Wie oben schon erwähnt: Bedeutet null nun, nicht initialisiert oder nicht zugewiesen oder nicht vorhanden oder kein Ergebnis oder Fehler ? Da jede Variable auch null sein kann, muss der Programmierer immer mitdenken, was für Zustände die Variable haben könnte und was diese im Kontext nun bedeutet. Vergisst er es oder missinterpretiert er es, hilft ihm der Compiler nicht, erst zur Laufzeit unter bestimmten Bedingungen (Zustände, Daten) kann es dann zu null-Exceptions kommen. Im Gegensatz dazu, mit dem Option Type ist es absolut klar und der Compiler weiss es auch und hilft!
union case Option.None: Option<'T>
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
union case Option.Some: Value: 'T -> Option<'T>

2014 das Jahr der Software Fehler und Sicherheitslecks?

Das Jahr 2014 beginnt mit der Aufdeckung der schlimmsten GOTO Fehlern in Betriebssystemen.

25. Februar : goto fail : Apple iOS, OS X :  SSL-Sicherheitsleck

Diese Lücke besteht angeblich seit 2012.
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) goto fail; goto fail;
Quellen: apple-mac-update-go-fail-ssl-bug , new-ios-flaw , gotofail.com

4. März : goto cleanup : Linux : “GnuTLS bug”

Diese Lücke besteht angeblich seit 2005 ! corrected return codes - Gitorious_2014-03-05_15-18-32 Expertenmeinungen dazu:
It looks pretty terrible. — Matt Green, Johns Hopkins University professor cryptography
has a lot of side effects. — Kenneth White, principal security engineer
Quellen: Sicherheitsluecke-GnuTLS , critical-crypto-bug-linux

Wie lange werden wir noch mit Software Fehlern leben müssen?

Es sind massive Sicherheitsrisiken! Software steckt in vielen Geräten. Anwender und Hersteller sind betroffen. Die von genervten Anwendern, die eine andere Software oder Geräte vorziehen, bis zu Daten-, Geld- und Imageverlust reichen können. Man denke auch an Bereiche der Personensicherheit und Medizintechnik, wo es auch um Menschenleben gehen kann! Wie lange werden wir auch noch mit einer anderen Fehlerquelle der NULL Ausnahmen (Null Pointer Exception, Null Reference Exception) leben müssen?
“I call it my billion-dollar mistake. It was the invention of the null reference in 1965.” — Tony Hoare
Diese Problematik betrifft nahezu alle heute noch verwendeten älteren Programmiersprachen wie z.B. C, C++, Java, VB.Net, C# ! Der Grund ist der Stammbaum, also die Abstammung der Programmiersprachen und deren weitergegebenen Konzepte. Quellen: Null PointerZeiger

Was können wir dagegen tun?

Wie kann man die Software Qualität in seinen Wurzeln verbessern? Indem man für die Softwareentwicklung eine Programmiersprache anwendet, die weder GOTO noch NULL benötigt, die kapitalen Fehler nicht zulässt und dabei den Entwickler aktiv und wachsam unterstützt, so z.B. die immer populärer werdende Programmiersprache F#, die hier auf Functional Software .NET das Thema ist.   Nebenbei bemerkt, wer eine TLS Implementation in F# betrachten will, kann dies hier tun “miTLS A verified reference TLS implementation” und es ist auf den Tag genau ein Jahr her, hier gibt es den F# Quellcode: “5 March 2013 Our paper on TLS security has been accepted at the IEEE Symposium on Security & Privacy. A draft technical report of this work is available from the download page.”. Siehe auch F7: Refinement Types for F#