14. Juni 2011

3.1.2013 further reading: Functional Programming

DE_flag GB_flag

for english readers

Das Lob der Faulheit

Wer fleißig ist, braucht keinen Rechner! Gib mal her, mach' ich schneller selbst und schon ist der Antrieb, sich mit Serienbriefen, Stilformaten, ServerSideIncludes oder gar Datenbanken auseinanderzusetzen, dahin. Schade drum.

Ich war ich fleißig, habe mich in den Schatten gesetzt oder in die Sauna, bin zum Schwimmen gegangen oder zur Kabarett-Bundesliga und meine Gedanken laufen lassen. Und, was ist bei rausgekommen? Konzepte die mir das Arbeiten mit 4D bequem machen. Sorry, bin gleich weg zum Schwimmen – darum nur kurz …

talsperre

… zu meinen wichtigsten Zeitsparern:

Konzeptionelle Blocks

Falte ich in einer Methode alles bis auf die erste Ebene zusammen, sind die Konzepte der Methode sofort zu erkennen. Hier sind das:

    blocks
  • Deklarationen und Parameter-Übergabe
  • Menüaufruf
  • Process-Verwaltung
  • Formular-Methode
  • Sichtbarkeit
  • Button-Verwaltung
  • Preferenzen
  • Suchen
  • Listboxen verwalten
  • Service
  • in diesem Beispiel fehlt
  • Felder triggern

Eine wichtige Entscheidung auf dem Weg zu den konzeptionellen Blocks fiel, als ich beschloß keine Methoden mehr in Formularen und Objekten anzulegen. Formulare und Objekte dürfen nur die eine Projekt-Methode aufrufen und Parameter übergeben. Jetzt finde ich alles aufgeräumt an einer Stelle, in einem Block. Dort finde ich, was ich suche auch noch nach Monaten. Es klappt erstaunlich gut.

leereMethode

Konzeptionelle Blocks sind nicht die Lösung für alle Anwendungszwecke. Viele Funktionen bleiben besser knapp und klar. Für dieses Verfahren eignen sich Modul-Methoden besonders. Module sind Adresse (Personen, Firmen, Postadresse(n), eMail, Fon/Fax, …) oder Rechnung oder Korrespondenz oder verallgemeinert: eine je wichtiger Tabelle.

Für die konzeptionellen Blocks eines Modulmanagers habe ich mir eine Handvoll Macros angelegt. 1, 2, 3, 4, 5, 6, 7 Macros ausführen und ich kann das Modul mit Klick auf den grünen Pfeil starten und testen.

Die Blocks

kopf

1. Kopf

Die Kopfinformationen sind für später, für den Kommentar und für diejenigen, die nach mir den Code anfassen: wie heißt die Methode, von wem wurde sie wann angelegt, welche Version ist es, welche Parameter erwartet sie und wo ist sie zu Ende.

Das Ende ist nicht der Kopf aber muß von Anfang an festgelegt sein, also akro was auch wieder oben, hoch oder Kopf bedeutet. Paßt schon!

Der Cursor wartet in der Bemerkungszeile. Wartet ein Kollege auf den Code, trage ich in den Bemerkungen die nötigen Infos ein, sonst drängt sich die Einsicht, nicht päpstlicher als der Papst sein zu müssen vor und ich laß' für später leer.

parameter

2. Deklarationen und Parameter

Variablen-Deklarationen will ich im Kopf der Methode haben. Neben den Parametern werden die wichtigsten lokalen Variablen deklariert. Über kurz oder lang brauche ich $i und $N und einige andere. Deklarierte Variablen schlägt mir der Methoden-Editor der V12 vor, die brauche ich nur mehr anzunehmen. Also Macro schaffe Du!

Die Parameter werden im Kopf mit Fehler-Kontrolle und Typ-Überprüfung lokalen Variablen zugewiesen. $1..n will ich weiter unter nicht mehr sehen. Da ein Macro Zuordnung und Fehlerkontrolle einträgt, braucht nichts Mühsames, keine Eventualität unter den Tisch zu fallen.

Nahezu vollständige Typdeklarationen per Macro einzutragen klappt, weil ich auf 1-2-3 Parameter standardisiert habe und nur ab und an weitere und/oder andere brauche. Die drei Parameter sind:

  1. was soll getan werden - Text
  2. an welchem Objekt - Pointer
  3. in welcher Zeile, für welche Datensatz-ID - Longint

parameterAufraeumen

Alle Deklarationen, die Parameter-Übergabe und -Überprüfung sind in ein If(True) - End if eingeschlossen, so daß ich diesen Teil leicht zuklappe, um die Übersicht zu erhöhen.

Glücklicherweise ist der Methoden-Editor der V12 in der Lage, sich den Einklapp-/Ausklapp-Zustand zu merken. Das ist eine Arbeitserleichterung, die mir alle Eckigkeit des neuen Editors aufwiegt. Mille merci!

Aufgeräumt ist der Rest des vom Parameter-Macro eingefügten Code zu sehen. Der große Case of-Verteiler mit dem Debug-Ende bei Else $keinDollarWhat_b. $keinDollarWhat_b wird wahr falls die Methode noch nicht kann, was $1 = $what von ihr erwartet. Dann kommt die Meldung es fehlt eine Tu was-Option die der Entwickler vor der Mattscheibe sofort bekommt. Ist er nicht da, wird das Fehlerprotokoll aufgefüllt.

3. Prozess starten und verwalten

Wird die Methode über den grünen Pfeil gestartet oder aus einem Menü aufgerufen, also ohne Parameter, landet sie in $what="". Dann prüft sie, ob es den Prozeß schon gibt. Gibt es ihn nicht, ruft die Methode sich selbst in einem neuen Prozeß auf und übergibt den Parameter "StartProcess". Wenn es den Prozeß schon gibt, wird er eingeblendet und nach vorne gebracht. Damit entfällt eine Extra-Methode, nur um den Prozeß zu starten.

Mit wenig Aufwand kann ich diesen Manager mehrmals in eigenen Prozessen starten. Dann verteile ich die Fenster über den Bildschirm und habe mehrere Ansichten in die Datenbank. Sehr kommod, fast ohne zusätzlichen Programmieraufwand.

formMethode

4. Formularmethode

Im nächsten Block werden alle Form-Events* abgedeckt. Das Formular ruft in seiner Methode Oeler_Mngr("FormMethod") auf. Sonst passiert da nix, gar nix.

Mit der V12 ist der Event On bound variable change neu und sofort wichtig geworden. Das ist der Event für meinen Zeitsparer Nr. 3: eingebundene Formulare (V12). Ich spare viel Aufwand, weil ich ein und dasselbe Formular alleinstehend und in einem/in vielen anderen Formularen eingebunden einsetze. Das ist eine Arbeitsersparnis, die sich um so mehr auswirkt, je mehr sich das Konzept zu einfenstrigen Formularen durchsetzt – also schon immer unter Windows und dem 4D MDI-Fenster und zunehmend in MacOSX, the lion sleeps tonight war gestern, und irgendwann für iOS.

In absehbarer Zeit wird das Macro ergänzt um die Auslagerung der Events On Load und On Validate. Ich brauche diese Events ebenfalls nach On bound variable change, deshalb macht das Sinn, sie nicht mehr im Event On Load und On Validate selbst abzuhandeln.

Erwähnenswert ist noch $closingForm_b. Wird das Formular nicht geschlossen, sind die Buttons upzudaten und ggf. die Sichtbarkeit einzustellen, der Fenstertitel anzupassen, also If(Not($closingForm_b)).

buttons

5. Buttons

Alle Buttons des Formular rufen nur die Manager-Methode auf, alle! Diese weiß, welche Buttons aktiv und sichtbar sind, welche müssen umbenannt werden. Finns'de sofort in Button_Enable - ganz ohne rumwühlen!

standardbutton

Die Standard-Button Aktionen sind Neu, Ändern, Löschen und der Aktions-Button. Button-Macro legt deren Aktionen mit an. Die Aktionen sind noch einzutragen, der Aktions-Button ist bereits an die Standard-Aktion popupmenu_Helper angeschlossen.

Platzhalter

… für Preferenzen, Suchen, Listbox-Verwaltung sind nach gleichem Schema eingerichtet:
:($what="Listbox_@")
Case of

  • :($what="@_Init")
  • :($what="@_Fill")
  • :($what="@_Action") oder :($what="@_Edit")
Else
  $keinDollarWhat_b:=True
End case

Preferenzen, Suchen, Listbox-Verwaltung sind DDDD-Themen für sich.

Als die Tabellen-Trigger bei 4D noch Tabellen-Prozeduren hießen, lagerte ich alle feldbezogenen Aktionen in eigene Trigger-Methoden aus. Damit hatte ich alle Aktionen nach Modified(Feld) an einer Stelle. Meine alten Trigger-Methoden sind heute ein wichtiger Block in vielen Manager-Methoden.

datensatztrigger

6. Datensatz-Trigger

Die Datensatz-Trigger werden in
:($what="FormMethod")
  :(Form event=On Load)
    Modul_Mngr("Trigger_Load")
  :(Form event=On Clicked) | :(Form event= On data change ))
    Modul_Mngr("Trigger_Modified")
  :(Form event=On Validate
    Modul_Mngr("Trigger_Save")
aufgerufen.

In den Events On Getting Focus und On After Edit werden spezielle Helper-Methoden aufgerufen. In On After Edit z.B. Type_Ahead.

Konsequenterweise verfahre ich in On bound variable change sinngemäß; vor dem Sichern wird von Modul_Mngr("Trigger_Save") alles Notwendige ausgeführt und dann erst der vorhandene Datensatz mit SAVE RECORD gesichert und der 4D Tabellen-Trigger angeworfen.**
Anschließend wird der benötigte Datensatz über $P_boundObj-> gefunden und per Modul_Mngr("Trigger_Load") alles Notwendige vorbereitet und geladen.

Wo bleibt die Faulheit?

Ich lebe von Projektarbeit. Dann sind die konzeptionellen Blocks essentiell. Ein Projekt beginnt mit einer großen Anstrengung und wird in wenigen Tagen oder Wochen produktionsreif. Danach folgt die Phase des regelmäßig hier und dort reingucken, Kleinkram ändern und ab und an ein Modul ergänzen.

Den größten Aufwand stecke ich in das Design effizienter Arbeitsplätze. Das ist ein iterativer Vorgang: ich schlage ein Design vor, der Anwender kann an Hand des Designs formulieren was fehlt oder anders laufen müßte. Ich ändere und den Code muß ich selten weiterentwickeln. Selbst komplette neue Formulare sind nicht tragisch. Auch im neuen Formular ruft dieses Module_Mngr("FormMethod") auf, die Buttons werden per Module_Mngr("Button_Add") aktiviert, die Popups verweisen auf sich selbst Module_Mngr("hlTreatment_Action";Self)…

Hier zahlt sich die Arbeit aus, jetzt kommt die Faulheit zum Zuge. Die Konzepte Modul-Manager und Code-Blocks machen sich bezahlt. myDockIch finde alles, weil ich nicht mehr suchen muß. Habe ich was schon mal gelöst, ist es mit wenigen Copy&Paste Aktionen aus einem Projekt in ein anderes übernommen. Blocks sind in sich unabhängig.

In der V12 kann ich Tabellen kopieren. Copy, paste, fertig, klasse!

* Mit alle Events meine ich so gut wie alle. Drag&Drop-Events handele ich in einem eigenen Event-Handler ab, der direkt aus Objekt aufgerufen wird. Das ist praktischer, seit Drag&Drop vom Finder möglich ist.

** Abbrechen war gestern!

 

DE_flag GB_flag

lieber auf deutsch

Being lazy

Laborious people need no Mac! Let me see, I'm faster doing it by hand and every stimulus, to care about how mail-merge works, what stylesheets are good for or why ServerSideIncludes or databases beyond a spreadsheet will help, are gone. It's a pitty.

I was hard at work, while sitting in the shadow, enjoying the sauna, going for a swim, listening to a comedian. While enjoying I've developed some concepts that make my 4D-life more comfortable. Sorry, I'm off for swimming, just a minute …

talsperre

to tell about my main time-savers:

  • Listbox and just one list-form for all the tables of the database (arrays in V2004, selection since V12)
  • saves me many many list-forms to build and maintain. Read more about the DBZ-component
  • conceptionel method-blocks
  • read more about on this page and
  • included forms of V12
  • to get an idea read Updating the User-Interface into today. That stuff is really addictive!

Conceptionel method-blocks

Folding a method up until first level, the concepts of a method get obvious. Those are:

    blocks
  • declarationen and parameter-passing
  • call from a menu
  • process management
  • form-method
  • visibility
  • button management
  • preferences
  • queries
  • listbox management
  • services
  • missing in this example
  • field triggers

Without conceptionel method-blocks I couldn't have abandoned all object- and form-methods. Objects and methods only call one single project-method and pass a parameter or two. Now everything is at one place in a defined block, very tidy. Finding even months later – without tons of comments to read before – works well.

leereMethode

Some functions better keep on their own. As soon as it looks like a modul, conceptionel method-blocks are a must. Moduls like addresses (Person, company, mail-address, email, phone/fax, …), or invoices, or correspondence to name a few need a single manager-method and method-blocks to keep them tidy.

Macros are the real time-savers. Calling 1, 2, 3, 4, 5, 6, 7 Macros and the manager-method can be run and tested immediately – go, green triangle, go.

The Blocks

kopf

1. Head

Header information is for the comments und for those working on the code later. What's the name of the method, who created them and when. which version and which parameters to pass and where does the method end.

The cursor blinks in the comment zone. Am I expecting a college to follow I'll put in some explanation, otherwise I'll do that later. Yes I know I should.

parameter

2. Declarations and parameters

I want my variables be declared in the header region of the methods. First all parameters are declared and a couple of local variables I need most of the time, like $i and $N. Side effect, the V12 method-editor will propose declared variables. Nice job for a macro.

Besides passing parameters to more expressive local variables, I check types and set defaults. Error-checking can be bullet-proof, because it's a macro that enters all lines of code not me.

This works for me with a macro because I've standardized on 1-2-3 parameters. Those are:

  1. what shall be done - Text
  2. which object - Pointer
  3. which line, which record-ID - Longint
Sometimes this sparse count of parameters doesn't work for manager-methods, pretty seldom indeed.

parameterAufraeumen

All declarations, parameter-passing and -checking are enclosed in a If (True) - End if to collapse that part.

Hard to believe it took until V12 for the method-editor to remember collapsed and expand-state of the method. This is such a helpful feature, thanks mille merci!

The main case-dispatcher and it's debugging Else $keinDollarWhat_b is seen when tidied up. $keinDollarWhat_b gets true, when $1 = $what asks for something that's not implemented yet. The developer in front of the screen gets an immediate alert there is this'n that missing. If it's not him or her, the message is send to an error-document.

3. Start process and manage it

Running the method from a menu or clicking the green triangle, in both cases no parameters are passed i.e. $what="". Then the method checks if that process is already running. If this is not the case, it'll call itself into a New process, passing parameter "StartProcess" to $what. If the process exists, it'll be shown and brought to the front.

If this method handles a workspace which need not be unique, I could easily enhance the code to run a couple of the same workspaces. Think about working on an offer and answering phone-calls of other customers in between: a couple of the same windows and manager-methods running side by side.

4. Formmethod

formMethode

All form events are handled in this code-block*. The form-method only calls Oeler_Mngr("FormMethod"). Nothing else, not at all.

New with V12 is the event On bound variable change and it got important immediately. This is the event for time-saver No 3: included forms of V12. Reusing the form this way in different environments is new to V12. That's the way to go. And more so, since single-window UIs are the trend – as it has been in Windows besides the plural s, and the MDI-Window of 4D anyway, nowadays in MacOSX Lion and iOS.

Not to forget $closingForm_b. While the window is active, buttons have to be updated, visibility has to be checked, the windowtitle should be up-to-date, which means If(Not($closingForm_b)) do all that.

5. Buttons

buttons

No button does anything of its own. Every button calls the manager-method, that's it. The button-block of the manager-method contains all button-relevant code. No searching for anything, it's all at its place.

standardbutton

There are a couple of standard-actions like new, modify, delete and action. Their behavior has to be entered at the appropriate spot, the action-button has a default-action attached by popupmenu_Helper.

Placeholders

… for Preferences, Queries, Listbox-management work similar to this example:
:($what="Listbox_@")
Case of

  • :($what="@_Init")
  • :($what="@_Fill")
  • :($what="@_Action") or :($what="@_Edit")
Else
  $keinDollarWhat_b:=True
End case

Preferences, Queries, Listbox-management are DDDD-themes in its own.

6. Record-Trigger

datensatztrigger

Since the early days of 4D tables could have procedures. I used my own triggermethods for every table to handle the field-actions at one place, like Trig_Person (max 15 chars!). Those ancient trigger-methods are now just one block of code inside the manager-methods.

All actions triggering the behaviour of fields are called like this:
:($what="FormMethod")
Case of
  :(Form event=On Load)
    Modul_Mngr("Trigger_Load")
  :(Form event=On Clicked) | :(Form event= On data change ))
    Modul_Mngr("Trigger_Modified")
  :(Form event=On Validate
    Modul_Mngr("Trigger_Save")

The events On Getting Focus and On After Edit call their own helper-methodes. I.e. On After Edit calls Type_Ahead for the object with the focus.

It's only consequent to proceed after On bound variable change accordingly. Before saving Modul_Mngr("Trigger_Save") will do all necessary stuff followed by the real SAVE RECORD which calls the 4D table-trigger.**
Then $P_boundObj-> loads the needed record and Modul_Mngr("Trigger_Load") prepares the record.

Does this sound like lazy?

Think so! I make my living from project-work. Thinking in conceptual manager-methods and method-blocks is essentiell for me. A project begins with a huge effort and achieves production-state in a couple of days or weeks. Then follows the phase of regularly checking, adding small pieces here and a module there.

Most of my time is invested into creating optimized workspaces, which is an iterative design-process. Iterating between my ideas and what the user can formulate as missing after testing my realisation. The code is pretty much stable from the beginning. Even exchanging forms by completely new ones is not a tragedy. Even the new form calls Module_Mngr("FormMethod"), the buttons activates by Module_Mngr("Button_Add"), the popups pass a pointer onto themselves Module_Mngr("hlTreatment_Action";Self), might even be renamed. Who cares?

Now the conceptual thinking and all that work invested pays. There is no looking for anything anymore, everything is at its place. myDockDid I develop a functionality already, it gets down to some copy&paste actions between two different projects. Code-blocks are independent.

V12 allows copying of tables. Copy, paste, done, superb!

* all means nearly all. Drag&Drop-events are handled more efficiently by its own D&D-manager, directly called from the object. This is more comfortable since drag & drop from and to the finder are functions, i.e return a value. And thou shall not have a $0 if you call a method from a menu. This will crash your 4D when compiled.

** Cancel is old-fashioned!