20101124

Growing Delphi code

I find Delphi as a language has its perks but doesn't come without annoyances. I'm not so much comparing it to other programming languages here (i would not know where to start), but take a look at how to not waste time or make bad code when you ARE using Delphi. This article is no less relevant if you use other languages though.

Its all about designing pillars, reusing them, and being able to replace them one by one with improved versions after the facts.
An application must be able to grow as its maintained, one pillar at a time.

Re-usability.
Everybody knows you must document your code, order and version your source files, have a code standard so i won't go into the obvious here concerning re-usability. But what do i mean when i say re-usability:
"The ability of a package or subprogram to be used again without modification as a building block in a different program from the one it was originally written for."
In dutch i often call this "draagbare code" aka portable code. Take it with you, plug it in, it should work without requiring even more code.

Package is the key word here, its what Delphi likes doing. Or moreover, it should be what you like doing. Delphi enables you to make packages of code, useful when you want to compile a DLL, when you develop a custom component. I presume its also useful as a library of code, but this Delphi has yet to prove me. It seems projects using a package lack full debug capabilities. This is because they only access the DCU file, not the PAS. I imagine this DCU file can be circumvented by compiling projects (or packages) another way (non-linked?), but i  am not sure yet. If you know of a way, please post it in the comments.

Do not cast your Delphi code into concrete. When building writing a unit that should be reusable, make sure not to:


  • Put your attributes and functions in the private section of your class. Your solution is a quick hack anyway, so it's better to hide it.
  • Don't declare a procedure of function virtual. Really, no one wants to override what you did anyway.
  • Use enumerated types. No one ever will have a need to extend this type.
  • Write procedures and functions in the implementation of your unit, and don't export them. They're not good enough to be reused anyway.
These are just the tip of the iceberg. Avoiding this may save you (and your coworkers) a headache.



Versioning shared units.
At time of writing (2010) Delphi RAD still lacks a version control solution i find decent. I now use Git, which doesn't integrate at all. This is difficult when files are used by more than one project. Using packages in Delphi gets you halfway, but not far enough. Consider this common scenario:

  • A unit "U" that implements function "F" returning a type "T".
  • A custom component "C" that when put on a form has a GUI to let the user use "F" from "U", thus returning "T".
  • A custom component "D" that when put on a form has a GUI to let the user see "T".
  • A project with a form that contains "C" and "D", and must know of "T" in order to have "D" display what "C" did.
  • Another project, using "T". Thus requiring "U".
  • Another component, using "T". Thus requiring "U".

On the last two lines I say "requiring" as if these should know about the PAS file. This is what i prefer, but it just about impossible to do with Delphi without making the source-file references that clutter your projects look like chaos. The reason i prefer this above DCU files, is because these don't enable me to debug their code.

But the real reason i put forward this scenario is to show you what problems arise when you try to do this in a controlled manner, using version control. How i imagine this should be done:

  • Develop unit "U" in a repository, once in a while spawning a stable branch.
  • Develop package (storing "C" and "D") in a repository, once in a while spawning a stable branch. When it spots a newer stable branch of "U" at compile, it informs me clearly and pulls it.
  • Develop project in a repository, once in a while spawning a stable branch.
Problem however is that Delphi is a jerk here and requires access to BPL, DCU, and PAS files. I've yet to figure out a way of arranging this so everything "just" works.


Folder structure as a remedy
I've tried lost of folder structures over the last few days, and regret having to resort to my initial setup.
I tried a huge repository containing all components and shared code (as a library package), and found it difficult to find a place for each components' test-project. Ultimately this structure was clear but Delphi still was a jerk about files it required in its search path.

Right now, i'm considering compiling all DCU files to a central folder like "\lib\" but still struggle with where to put that folder. I believe that code should be kept together, and as such that DCU's (except for the VCL and RTL lib ofcourse) in my program files folder should have no place in my application. I want to be able to just open the same code on a different computer and be able to compile everything exactly as it was before.

There's lots of resources on what folder structure a Delphi project could be kept in, so to add on those here is an attempt to share my way of structuring this.

  • / - The root of this package or project. Contains my DPROJ file, RES file, and such.
  • /generated/ - contains everything Delphi makes up from my code. (and contains dcu, bpl, bin folders)
  • /src/ - contains my source code.

I always put a testproject alongside a package, and have a ProjectGroup assigned to both DPROJ files to easily open it as a whole. The whole shebang is then saved in some form of version control.

(Disclaimer: to my liking this article is far from finished, but i decided to publish it anyway whilst i'm diving further in this matter. Contributing comments will be amended.)

References: