Delphi Legacy Projects - Strategies and Survival Guide
Delphi Legacy Projects - Strategies and Survival Guide
William Meyer
Meyer Design
Disclaimer
All product names, logos, and brands are property of their respective owners. All
company, product and service names used in this website are for identification
purposes only. Use of these names, logos, and brands does not imply endorsement.
Colophon
This document was typeset in LATEX with the help of KOMA-Script and LATEX
using the kaobook class.
Publisher
First printed June 24, 2022 by Meyer Design
ISBN: 979-8833375693
Dedicated to those who labor in the fields of legacy
code, and to my wife Lin, whose patience with this
project was essential to its completion.
Contents
Contents v
Preface 1
1 Frame of Reference 3
1.1 Your Current Practice . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 More Thoughtful Coding . . . . . . . . . . . . . . . . . . . . . . 3
3 Strategies 21
3.1 Philosophy and Reality . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Setting Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.1 Patience . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.3 Select Your Components . . . . . . . . . . . . . . . . . . . . . . 23
3.4 Pre-convert, if Necessary . . . . . . . . . . . . . . . . . . . . . . 23
3.5 Isolate Your Code . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.6 Achieving a Build . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.7 Module Inclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.8 Separating Projects - How and Why . . . . . . . . . . . . . . . . 28
3.8.1 Multiple Projects in One Delphi Version . . . . . . . . . . 29
3.8.2 Multiple Projects in Multiple Delphi Versions . . . . . . . 30
3.9 Start Small . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.10 Incremental Change . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.11 Strip Deadwood . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4 Beginning 35
4.1 The IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2 The Build Process . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2.1 Build Management . . . . . . . . . . . . . . . . . . . . . 36
4.2.2 Accept Compiler Advice . . . . . . . . . . . . . . . . . . 36
4.3 Data and Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.1 Scope of Data . . . . . . . . . . . . . . . . . . . . . . . . 41
4.3.2 Writeable “Constants” . . . . . . . . . . . . . . . . . . . 43
4.4 Scope in General . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.4.1 Forms vs. Units . . . . . . . . . . . . . . . . . . . . . . . 46
4.4.2 Proper to Forms . . . . . . . . . . . . . . . . . . . . . . . 46
4.4.3 Proper to Class Units . . . . . . . . . . . . . . . . . . . . 47
4.4.4 Proper to Data Modules . . . . . . . . . . . . . . . . . . 48
4.4.5 Everything Private! . . . . . . . . . . . . . . . . . . . . . 48
4.4.6 Global not Always Bad . . . . . . . . . . . . . . . . . . . 49
4.4.7 Partitioning . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.5 Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.5.1 A Simple Example . . . . . . . . . . . . . . . . . . . . . . 51
4.5.2 Exceptional Failures . . . . . . . . . . . . . . . . . . . . . 52
4.5.3 Exceptional Confusion . . . . . . . . . . . . . . . . . . . 55
4.5.4 Scope of Exception Handlers . . . . . . . . . . . . . . . . 57
4.5.5 Default Exception Handlers . . . . . . . . . . . . . . . . 58
4.6 Code Formatting . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.6.1 Formatting Matters . . . . . . . . . . . . . . . . . . . . . 60
4.6.2 Manageable Formatting . . . . . . . . . . . . . . . . . . . 63
4.6.3 Minor Formatting . . . . . . . . . . . . . . . . . . . . . . 64
Digging Into the Challenges 65
5 Comments 67
5.1 Useless Comments . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.2 Annoying Comments . . . . . . . . . . . . . . . . . . . . . . . . 67
5.3 Thoughtless Comments . . . . . . . . . . . . . . . . . . . . . . . 68
5.4 Essential Comments . . . . . . . . . . . . . . . . . . . . . . . . . 69
7 Simple Things 89
7.1 Structural Changes in Delphi . . . . . . . . . . . . . . . . . . . . 89
7.2 Hints and Warnings . . . . . . . . . . . . . . . . . . . . . . . . . 90
7.3 Types and Constants . . . . . . . . . . . . . . . . . . . . . . . . 91
7.3.1 No Magic Numbers . . . . . . . . . . . . . . . . . . . . . 91
7.3.2 Unicode? . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
7.3.3 Numeric Type Aliases . . . . . . . . . . . . . . . . . . . . 93
7.3.4 Global can be OK . . . . . . . . . . . . . . . . . . . . . . 94
7.3.5 Assignment Compatibility . . . . . . . . . . . . . . . . . 95
7.3.6 Use Enumerations . . . . . . . . . . . . . . . . . . . . . . 96
7.3.7 Use Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
7.3.8 Use Defined Ranges . . . . . . . . . . . . . . . . . . . . . 99
7.4 Variables and Scope . . . . . . . . . . . . . . . . . . . . . . . . . 100
7.4.1 Avoid Global Variables . . . . . . . . . . . . . . . . . . . 101
7.4.2 Using AOwner . . . . . . . . . . . . . . . . . . . . . . . . 101
7.5 Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
7.5.1 Form Variables . . . . . . . . . . . . . . . . . . . . . . . . 104
7.5.2 Form File Format . . . . . . . . . . . . . . . . . . . . . . 104
7.6 Delphi Versions . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
11 Refactoring 155
11.1 A Workable Approach to Small Change . . . . . . . . . . . . . . 157
11.2 The Value of a Data Module . . . . . . . . . . . . . . . . . . . . 158
11.3 Testing in a Data Module . . . . . . . . . . . . . . . . . . . . . . 159
11.3.1 Information Hiding . . . . . . . . . . . . . . . . . . . . . 159
11.3.2 Minimizing Points of Contact . . . . . . . . . . . . . . . 160
11.4 The Challenge of Testing . . . . . . . . . . . . . . . . . . . . . . 161
11.4.1 Massive Routines . . . . . . . . . . . . . . . . . . . . . . 162
11.4.2 Massive Coupling . . . . . . . . . . . . . . . . . . . . . . 162
11.4.3 Risk is Unavoidable . . . . . . . . . . . . . . . . . . . . . 163
11.5 Code for the Maintainer . . . . . . . . . . . . . . . . . . . . . . 164
11.5.1 Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
11.6 Prefer Library Code . . . . . . . . . . . . . . . . . . . . . . . . . 165
11.7 Use Nested Routines . . . . . . . . . . . . . . . . . . . . . . . . 166
11.8 Extract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
11.9 Prefer Composition . . . . . . . . . . . . . . . . . . . . . . . . . 167
11.9.1 The Public Interface . . . . . . . . . . . . . . . . . . . . . 168
11.9.2 The Private Behaviors . . . . . . . . . . . . . . . . . . . . 172
11.9.3 Notes on Implementation . . . . . . . . . . . . . . . . . . 175
12 Removing Code from Forms 177
12.1 Extract Utility Routines . . . . . . . . . . . . . . . . . . . . . . . 177
12.1.1 Refactoring Praxis . . . . . . . . . . . . . . . . . . . . . . 178
12.2 Building Utility Units . . . . . . . . . . . . . . . . . . . . . . . . 179
12.3 Shared Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
12.4 Use Data Modules . . . . . . . . . . . . . . . . . . . . . . . . . . 181
12.4.1 Common Operations . . . . . . . . . . . . . . . . . . . . 181
12.5 Separate Business Logic . . . . . . . . . . . . . . . . . . . . . . 183
12.5.1 What is Business Logic? . . . . . . . . . . . . . . . . . . . 183
12.5.2 Organization . . . . . . . . . . . . . . . . . . . . . . . . . 185
12.5.3 Separate Data Handling . . . . . . . . . . . . . . . . . . 186
16 Pragmatism 215
16.1 Assessing the Costs . . . . . . . . . . . . . . . . . . . . . . . . . 216
16.2 Measuring Value . . . . . . . . . . . . . . . . . . . . . . . . . . 216
16.2.1 Use EurekaLog . . . . . . . . . . . . . . . . . . . . . . . . 217
16.2.2 Use Analytics . . . . . . . . . . . . . . . . . . . . . . . . 217
16.3 Missing Source Code . . . . . . . . . . . . . . . . . . . . . . . . 218
16.4 Analytical Modules . . . . . . . . . . . . . . . . . . . . . . . . . 219
16.5 Little-Used Modules . . . . . . . . . . . . . . . . . . . . . . . . 219
17 Interfaces 221
17.1 History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
17.2 Reducing Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
17.2.1 Constants and Types . . . . . . . . . . . . . . . . . . . . 222
17.2.2 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . 223
17.3 Managing Memory . . . . . . . . . . . . . . . . . . . . . . . . . 227
17.4 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
17.5 Going Deeper . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
17.5.1 Variations . . . . . . . . . . . . . . . . . . . . . . . . . . 235
18 Testability 237
18.1 Testing Legacy Code . . . . . . . . . . . . . . . . . . . . . . . . 237
18.2 Layers of Difficulty . . . . . . . . . . . . . . . . . . . . . . . . . 237
18.3 Design Specifications . . . . . . . . . . . . . . . . . . . . . . . . 238
18.4 Interdependency Issues . . . . . . . . . . . . . . . . . . . . . . . 239
18.5 Coupling, Partitioning . . . . . . . . . . . . . . . . . . . . . . . 239
18.6 Code on Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
18.7 Testability Defined . . . . . . . . . . . . . . . . . . . . . . . . . 240
18.8 Ensure Testability . . . . . . . . . . . . . . . . . . . . . . . . . . 241
18.9 Unit Test Frameworks . . . . . . . . . . . . . . . . . . . . . . . . 241
18.10 Testing Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
19 Performance 245
19.1 Realities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
19.1.1 Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
19.1.2 Profiling Legacy Code . . . . . . . . . . . . . . . . . . . 246
19.1.3 Approaching Profiling . . . . . . . . . . . . . . . . . . . 247
19.2 Painful Realities . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
19.3 You Need Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
22 SOLID 273
22.1 What is SOLID? . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
22.1.1 Single Responsibility . . . . . . . . . . . . . . . . . . . . 274
22.1.2 Open/Closed . . . . . . . . . . . . . . . . . . . . . . . . 275
22.1.3 Liskov Substitution . . . . . . . . . . . . . . . . . . . . . 276
22.1.4 Interface Segregation Principle . . . . . . . . . . . . . . . . 277
22.1.5 Dependency Inversion . . . . . . . . . . . . . . . . . . . 278
28 CnPack 325
28.1 Structural Highlighting . . . . . . . . . . . . . . . . . . . . . . . 326
28.2 Tab Order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
28.3 Uses Cleaner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
28.4 CnPack Summary . . . . . . . . . . . . . . . . . . . . . . . . . . 330
29 CodeSite 331
29.1 Details in Your Hands . . . . . . . . . . . . . . . . . . . . . . . . 331
29.2 Data from the Field . . . . . . . . . . . . . . . . . . . . . . . . . 333
29.3 Simple Example . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
33 DUNIT 347
33.1 DUnit in Recent IDEs . . . . . . . . . . . . . . . . . . . . . . . . 348
33.2 Developing Test Cases . . . . . . . . . . . . . . . . . . . . . . . 352
33.3 Start with the Familiar . . . . . . . . . . . . . . . . . . . . . . . 355
33.4 Coverage in Legacy Projects . . . . . . . . . . . . . . . . . . . . 356
33.5 Test Driven Development . . . . . . . . . . . . . . . . . . . . . . 357
33.6 Unit Test Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . 358
33.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
34 DUnit2 361
35 DUnitX 365
35.1 Looking at Some Code . . . . . . . . . . . . . . . . . . . . . . . 367
35.1.1 DUnit vs. DUnitX Features . . . . . . . . . . . . . . . . . 375
35.2 DUnit vs. DUnitX . . . . . . . . . . . . . . . . . . . . . . . . . . 375
35.3 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
36 FixInsight 379
36.1 Real World Data . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
37 GExperts 383
37.1 Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
37.2 Editor Experts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
37.3 Replace Components . . . . . . . . . . . . . . . . . . . . . . . . 387
37.4 GREP Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
37.5 GExperts Summary . . . . . . . . . . . . . . . . . . . . . . . . . 389
39 MapFileStats 425
40 MMX 427
40.1 Add Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
40.2 Add Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
40.3 Add Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
40.4 Sort Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
40.5 Synchronize Signatures . . . . . . . . . . . . . . . . . . . . . . . 432
40.6 Swap Scopes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
40.7 Unit Dependency Analyzer . . . . . . . . . . . . . . . . . . . . . 433
40.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434
42 ProDelphi 443
42.1 Profiling Some Demo Code . . . . . . . . . . . . . . . . . . . . . 443
42.1.1 Considering ExportDataSetToCSV . . . . . . . . . . . . . . 446
42.2 Profiling, in General . . . . . . . . . . . . . . . . . . . . . . . . . 447
42.2.1 Why Profile, Why Optimize? . . . . . . . . . . . . . . . . 447
43 TestInsight 449
Bibliography 455
William Meyer
June 24, 2022
Frame of Reference 1
Before beginning to present my approach to legacy projects, I 1.1 Your Current Practice . . 3
think it may be helpful to consider the frame of reference which 1.2 More Thoughtful Coding 3
is needed for success in such projects. Delphi is the toolset, but
you may find in legacy projects evidence of coding which began
in Turbo Pascal. No doubt you have picked up this book in the
hope of finding a better way of attacking the problems you face.
Hopefully, much of what I offer here will be useful.
The single most important principle I would drive home is that You will see that I use these side
you must always leave a code module better than you found it. notes to emphasize, often through
repetition. Might as well start early!
You will need to settle for incremental change; evolution, not
revolution. Every little improvement makes a difference, and over
time, those improvements accumulate to reshape your code. In
4 1 Frame of Reference
this volume, I will present strategies for such change, and propose
specific ways to alter things. But none of that will be of much use
unless you are able to adapt your perspective.
Victories
Savor the victories, however small they may be. This is a
marathon, not a sprint.
of the data we will use before we begin coding. Well organized data
not only simplifies and often reduces the code needed, but may
also help with the Separation of Concerns. (See chapter 21.4).
Introducing the Problem 2
When Delphi was introduced, the IDE was astonishing in its 2.1 Purpose and Perspec-
capabilities. Visual Basic came on the scene in 1991 and the IDE tive . . . . . . . . . . . . 9
was a rough first attempt at a new paradigm. 2.2 Areas of Difficulty . . 9
2.2.1 Evolution, not Revolu-
Delphi 1, in 1995, got it right, particularly in the implementation of
tion . . . . . . . . . . . . 10
two-way tools. Over the years, however, there have been missteps,
including buggy releases, and there have been defects in the IDE 2.3 We May Be the Enemy 12
2.3.1 Too Many Compo-
which remained for far too long. In spite of these issues, Delphi
nents? . . . . . . . . . . 12
grew stronger and more capable, with versions Delphi 3, Delphi 5,
2.3.2 Abusing the Search
and Delphi 7, each being remarkable products. Path . . . . . . . . . . . 13
2.3.3 Coding Too Soon? . . . 14
Some History 2.3.4 Inheritance . . . . . . . 15
2.3.5 Excessive Coupling . . 17
Delphi, so different from prior Pascal compilers, was a long 2.3.6 Unit Dependency
time coming, both in dreams and evolution. We owe a great Cycles . . . . . . . . . . 17
debt to Anders Hejlsberg and his team. 2.4 The IDE, Again . . . . 19
For those who came late to the
adventure, see this article:
The rumors of the death of Delphi continue to be greatly exag- https://en.wikipedia.org/wiki/
gerated. The language has remained in development, and new Anders_Hejlsberg
releases have added such features as generics, attributes, anony-
mous methods, and inline variables.
As with any software language, the quality of the result depends
on the knowledge and skill of the practitioners. We must keep in
mind that computers, operating systems, and language tools are
perpetually changing. It is easy to forget, at times, that forty years
ago, most software for small computers was written in assembly
language, and that most of what we think we understand of
programming came later. The C language was around, but early
versions running on small computers—they were not yet PCs—
were fairly painful to use. Turbo Pascal arrived in 1983, and put
the lie to then-conventional wisdom about compiled languages on
small target machines. Turbo Pascal for Windows didn’t come until
1991, but then, Windows 1.0 was 1985, and Windows 3.0 (the first
which didn’t frustrate endlessly) in 1990.
While all of this was going on, the pundits offered endless fads
about how to develop software:
8 2 Introducing the Problem
▶ Waterfall
▶ Top-down
▶ Bottom-up
▶ Spiral
▶ Structured programming
▶ Nassi-Shneiderman diagrams
▶ Warnier-Orr diagrams
▶ Unified Modeling Language (UML)
And there have been others, too numerous to count. There will
be disagreements over calling these fads, but at one time, each of
these was the definitive answer to the problem, and now it is not.
Perhaps fashions would be a less contentious description.
Waterfall Persists
To be fair, waterfall was not and is not a fad. Despite all
arguments over other approaches, there are huge projects which
are always managed in waterfall, and probably always will be.
Examples include the systems on major airplanes, especially
military models, where everything is and must be planned and
specified before work begins. And more significantly, where
the agile approach of leaving out unfinished features will never
fly. (Pun intended.)
Slimming down the component collection can be a challenge. There have been components in use
Panels and labels will usually be pretty easy, but the more complex which presented unusual capabili-
ties, and when those have been fully
the component, the more likely that some design rework will exploited, replacing the component
be needed. You may find it helpful to extract from the map file may be a major challenge. The incen-
the identities of components actually used in your executable. tive may also be great, as the vendor
Replacing one type with another, however, even for panels and may have disappeared, leaving the
component an orphan.
labels, will require visiting each of the forms in question. The
Replace Component function in GExperts is a help for such work.
Form inheritance can make the otherwise simple task of replacing
components more difficult.
Nothing in Delphi was added arbitrarily, and that includes the use
of the Search Path. However, it may be used, or it may be abused.
When the DPR file in a large project is kept rather small, leaving
the IDE to find many of the project’s modules from the Search
Path, then the responsiveness of the IDE is adversely affected. The
Code Insight features become sluggish, or even fail. Ctrl-click on a
method name may not take you to the unit in question.
Your life will be simpler if you do
Some of the issues can be remedied through use of plug-ins such not fight against the way the tools
were designed to be used. Inclusion
as GExperts, or CnPack, but a more direct solution is to add to the of your modules in the DPR file is
project explicitly each of the units which has been created for the a case in point. And though there
project. After all, Delphi did that when they were created; someone are reasons to edit the DPR file by
elected to remove them and use the Search Path instead. hand, adding and removing project
modules is not one of them.
14 2 Introducing the Problem
So in this book, you may assume that the strategies are based
on Windows projects. And after all, the horrible things we see in
legacy applications belong to the distant past, right? Who among
us would write such code on Windows, much less for mobile
targets?
For decades, we’ve read one book after another which harped on
first creating a design, then coding. Delphi does not make that less
essential. Despite the attention given to design, we are all subject
to some impatience to see results. Although it is certainly possible
to begin coding right away, you must be willing—even eager—to
refactor that code repeatedly. And such refactoring must always
consider that the routine you are working on may be in the wrong
unit or class. Ignoring those realities will lead to maintenance
issues, such as:
▶ Duplicated code
▶ Too-wide scope (an issue I will raise repeatedly)
▶ Unit dependency cycles (see below)
2.3.4 Inheritance
larger the hierarchy, the greater the complexity, and the greater
the constraints. Inheritance is not a Bad ThingTM , but it should not
be—as it became for a time—the most used tool in our kit.
Early on we learned that although you can easily make public an
inherited method, you cannot reduce the scope of a method. So if
you decide you want to inherit from TStringList, every one of its
myriad methods and properties will become a part of your public
interface. Messy.
Composition is a more flexible option than inheritance. Inheritance
is described as an is a relation; composition as a has a relation.
It is more common now to refer to aggregation or composition,
and some will argue over the specifics of each. When you rely
on inheritance, you get the full family tree—all the ancestors are
contained in each descendant. When you choose aggregation or
The specific term is of less conse- composition, you need not expose anything but what you choose
quence than the notion it represents. I to apply. You may use a class instance within your own class, and
am more likely to refer to composition,
which clearly implies intentionality,
not make public any of its properties or methods. You get the
than to aggregation, which seems to benefit of what the class can do, but limit the interaction with
me less thoughtful. consumers to the scope of operations you designed your class to
support.
More recently, the trend has been to favor composition, an approach
in which your class may contain instances of other classes, while
not inheriting from them. What is hidden in your class may be
complex, but the complexity of contained classes is not public
unless you make it so. One consequence of this is that designing
a class using composition is a good deal easier than when you
Name collisions will not be an issue, use inheritance. There are no name collisions to worry over, so
but conceptual collisions may arise, your method and property names can be exactly what you choose.
nonetheless. Exposing a name used
in other classes and giving it a differ-
Moreover, there is no real limit to the number of classes you may
ent behavior will win you no friends. choose to make members of your own, unlike the limit of the
single inheritance rule.
You may also find it useful to create a new class that encapsulates
for the sake of constraint a much larger class, such as the ubiquitous
TStringList. That has become a minor idiom for me, and besides
making the focus very clear, it presents an opportunity to create
an interfaced class, thereby removing the requirement for manual
memory management.
2.3 We May Be the Enemy 17
In Delphi, if Unit One needs access to code in Unit Two, then that
access is gained through adding a uses clause:
unit UnitOne ;
interface
uses
UnitTwo ;
implementation
end .
All well and good, and if the modules are well designed, no ill
effects will result. However, consider what happens when Unit
One uses Unit Two, and Unit Two, in turn needs a method from
Unit One. This can be resolved with a second uses clause:
unit UnitTwo ;
interface
implementation
uses
UnitOne ;
end .
18 2 Introducing the Problem
The MMX Unit Dependency tool (see Appendix) would report the
unit cycles thus:
Cycles for UnitOne (1)
UnitOne,UnitTwo,UnitOne
Cycles for UnitTwo (1)
UnitTwo,UnitOne,UnitTwo
This is not the end of the world by any means, but as the project
complexity increases, so does the depth of such cycles. When you
are working with 1,000 or more units, and the majority of them are
participating in dozens (or more) of such cycles, and the length
of the cycles is dozens (or more) units, then you have a real snarl.
And worse, you will find that the days of building in seconds are
long gone, and that building the application now takes minutes.
In the main, Unit Dependency Cycles are the product of poor design.
Returning to the simple case given above, a solution might be to
create a Unit Three which contains the method needed by both
Unit One and Unit Two. Then the picture in Unit One would
reduce to this:
unit UnitOne ;
interface
uses
UnitTwo , UnitThree ;
implementation
end .
unit UnitTwo ;
interface
uses
2.4 The IDE, Again 19
UnitThree ;
implementation
end .
Unit Dependency Cycles also affect the IDE. As soon as you type a
character in a code module, the prior state of the code as the IDE
understood it is now broken. The IDE then begins the reconstruction As I write this, Delphi 10.4 Sydney
of parse trees, and you will see a busy cursor indicating the IDE is has just been released, and as it im-
plements the Language Server Pro-
not ready for input. What we do not see is all that is happening tocol (LSP), the visual behavior will
behind the scenes. The interactive behaviors of the IDE depend be different, and reportedly will not
for their performance on the state of the parse trees, and hence the present the busy cursor.
delay when they must be rebuilt.
Rebuilding parse trees means that for each unit, the uses clauses
must be visited, leading to other units which must again be parsed.
Units which are included in the project are found using the paths
included in the declarations in the DPR file; the rest must be
discovered through the Search Path.
Delphi library modules need not be searched for, as they are known
to Delphi through registry entries. Similarly, components installed
using the installation tools provided with them will have registry
entries in place, and are in the collection of known packages.
What is essential is that the units we create in our own applications
should be added explicitly to the project. When we create a new
unit, form, or data module, Delphi adds it to the project, making
the entry in the DPR which includes path information.
If we make use of a unit of our own which is not declared a member In our code, entropy increases. We
of the project, then the Search Path provides the means of finding make changes, and in so doing, we
introduce defects. This is especially
the files. None of this need concern us much in small projects, but a concern when we lack unit tests. In
in large projects, these issues are important. And in extreme cases, legacy projects, a lack of unit tests
is the rule, not the exception, and
you can’t just create a collection of
them when much of your code is
untestable.
20 2 Introducing the Problem
Entropy is Reality
which are not apparent from the high-level view. As we discover 3.11 Strip Deadwood . . . . 32
them, we will need to rethink and be flexible. Bringing order out
of chaos will require patience, persistence, and perseverance. We
will meet challenges and obstacles.
There are useful strategies for attacking the problem of a large
legacy project, and this chapter will be a whirlwind tour. The
details comprise the rest of the book.
▶ Set your goals
▶ Select components with care
▶ Isolate from unnecessary code
▶ Pragmatically achieve a build
▶ Include all project modules in the project
▶ Start small
▶ Plan on incremental change
▶ Strip away the deadwood
22 3 Strategies
3.2.1 Patience
You may now have identified components which are in use and
which you will not install in the newer Delphi. If that is so, you will
want to pre-stage to a branch in your existing repository where
you can replace these components with others which will be in
the newer Delphi.
24 3 Strategies
If you have not managed such a mi- It is much easier to pre-stage, since you will be replacing one
gration before, this may be less than component with another. GExperts can help. Otherwise, when you
obvious. Changing out components
you do not want for those you do
move forms to the new Delphi where components are missing, the
is much easier in the environment forms rework will be more difficult. In some cases, architectural
in which you are able to build and changes will prevent GExperts form being useful. For example,
test. Forms moved across with com- some years ago, DevExpress made substantial changes in the
ponents which are not present in the
new Delphi will force you into a re-
architecture of some of their components. The net benefit to
design operation. customers was large, but came at a cost. They do provide a tool
to assist in converting from the old grids to new, but changes in
coding remain essential.
Plan to make this new repository support just one project. Your
old code base may have multiple projects in the same tree, and More detail will follow, but consider
that adds complexity, slowing your progress. Keep the one you that often multiple projects inhabit
a single folder tree. Although this
will be focused on and toss the rest. Once you have tamed the may seem logical in the beginning,
beast, you can revisit the issue of other projects to be brought in, it becomes very difficult to manage,
but it will be in the context of a clean house. and leads to issues with coupling and
UDCs.
If you make your new repository for a single project, you will be
able to remove unneeded modules as you achieve a build. Over
time, you may need to migrate one or more other projects, and
should follow the same approach. Eventually, you will extract
modules which are common to multiple projects into a shared
sub-tree, where they are easily recognized as affecting multiple
projects. This will be a boon to maintenance later.
With this new repository, you are now free to take a few essential
steps:
▶ Remove unneeded DPR and DPROJ files. (You need just one
of each.)
▶ Ensure all the component sets you selected for the new
environment are in place.
▶ Prepare a plan for documenting what you do, and what will
need to be done later.
26 3 Strategies
Now begins the tedious process of making the project build. Oh,
and in Project Options, disable writeable constants. Although in
formal terms, these are called typed constants, I prefer to call them
writeable, as that is their essential feature—they are not actually
constants.
▶ Deal with other issues as they arise. Don’t take the easiest
path, as you will need to revisit these later. At worst, if you
must comment something out, then place a comment with
your name and the date, as well as an explanation. And add
a TODO item.
How long will it take to reach buildable code? You will be asked. Are you unnecessarily rebuilding
The honest response is: ”I don’t know.” You can guess, or estimate, components when you build your ap-
plication? Check in the folder where
but depending on how you are doing the migration, you may be you direct your DCUs. If you are,
wildly wrong. You know the line count in the old version, so that then the compiler line count will be
should help, right? Maybe. But it is only going to be close if you different than you expect, and the
have replicated all of the dozens of factors which affect how that build time will be longer.
line count is achieved. Once you have passed the old line count
with no end in sight, what will your answer be?
What you may find at least moderately helpful is to save the
messages from a build in the old compiler, and compare with the
messages from the new one. But you are likely to find the new
compiler emitting hints and warnings relating to the increasingly
strict typing. So even this will be of little help, unless you filter out
all but the messages which identify the modules. Then you would
get some sense of progress.
The scenario above still applies, but with adjustments. Begin with
Project A, as before.
Project B may be the same, if it is presently in the same Delphi
version. However, if it is in a different Delphi environment, then
things are somewhat different. Copy the full set of folders needed
for the project into your new Project B folder.
Project B has a different DPR and DPROJ, and will use a different
search path. None of this is troublesome. However, there are two
areas in need of special understanding:
3.8 Separating Projects - How and Why 31
Once you have achieved a build, you probably will have amassed
a list of things you want to change. That’s great, and a normal
part of the process. But don’t go crazy, work with incremental
changes.
A likely area for rework is in forms appearance. You may have
forms which are inconsistent in color, dialogs on which OK and
Cancel buttons are not consistently placed, a mish-mash of different
brands of checkboxes and radiobuttons.
Rule: If you edit a module, leave it better than you found it. No
exceptions.
As your project is almost certainly not ready for unit testing,
you must focus on making small changes. A rewrite is high risk,
but small tweaks here and there will be relatively safe. This is
particularly true when you are factoring out nested routines from
long and ungainly old code, as the nested routines will tend to be
very focused, and easy to verify visually. Moreover, if you name
them well, the calls which replace the old inline code will serve to
make the entire routine more comprehensible. See A Workable
Approach to Small Change on page 157 for more detail.
Now for some serious housecleaning. This had to wait for the
project to build. Start by having a look at ProjectFiles Tool. The
Project Files tool will help you to find and remove files with
duplicate names, and unused source modules. In a large legacy
project the number of dead files can be surprisingly large. And all
the modules you remove are modules you need not maintain.
If you are using MMX (and if you are not, you should be) then
you can use the MMX Linked Module Analyzer to produce a list of
modules linked into your project. Compare this list to the modules
present in your project tree and remove those not needed. Be
3.11 Strip Deadwood 33
When Delphi was introduced, the build speed was amazing. For
small applications, that remains the case. But in large legacy
applications, there are numerous factors which affect the build
time, and you can certainly expect it to degrade over time. Entropy
increases, unless we take active steps to reverse it, not once, but
every time we touch a module.
These will be discussed later in some depth, but you really should
consider turning them all on in your debug configuration.
var
MyForm : TMyUsefulForm ;
begin
MyForm := TMyUsefulForm . Create ( nil ); // AOwner
try
// the important operations are here ...
finally
MyForm . Free ;
end ;
var
MyForm : TMyUsefulForm ;
begin
38 4 Beginning
This seems as good a place as any Passing Self to the constructor assigns the current form as the
to mention that although names owner of the dynamically created form. The owner has the respon-
like MyForm and TMyForm have long
been used in books and articles about
sibility to dispose of such objects when it is freed. This is as safe a
Delphi, they really ought never to be mechanism as the use of try/finally, and adds no clutter to code.
names you would use in your appli- It is possible that the dynamic form may be called more than once
cation. A pet peeve, but as names are in the life of the calling form, and you will want this pattern in
important, do consider it.
such instances:
var
MyForm : TMyUsefulForm ;
begin
if not Assigned ( MyForm ) then
MyForm := TMyUsefulForm . Create ( Self );
// the important operations are here ...
You must also design that dynamic form to be free of any persistent
state which could cause different behaviors on subsequent calls.
The options above impose some degree of performance overhead,
but should really be used, at least in your debug configuration.
Note that range and overflow checking are off by default, probably
because they incur performance overhead. It is a very good idea,
however, to enable them in your debug configuration at least, as
their use can help you to discover and correct defects before your
users do.
Perhaps you never fail to check such issues in your code, but in
legacy code, you can bet someone has failed to ensure that an array
bound is respected, or an index is calculated without checking
that the resulting value is in range. Delphi can help, and although
in new code you will want to be proactive, you must still survive
the horrors of code you have not yet even read.
Summary
Whether you are in the most recent Delphi version, or an older one,
the build time is something which you can observe as an indicator
of project health. If it takes minutes to build your project, you need
to do some work!
4.3 Data and Code 39
Many people would assume from the start that this section referred
to data as found in a database, but as the example shows, data
can be very simple. Keep in mind that data can often simplify
Magic numbers refers to the use in coding, and be watchful for such opportunities. Moreover, the
code of numeric literals which com- removal of strings from code is usually an improvement, though
municate no obvious meaning. For
example, although the fourth column
some may find it less obviously one than the elimination of magic
in your data may be ‘Description’, numbers.
coding a reference to a numeric index
is far less useful than a reference to a And though I refer to magic numbers the principle can be applied as
constant named colDescription. well to the use of hard-coded strings, especially where such strings
are used repeatedly. It would seem obvious that these should be
declared as named constants.
In this discussion, data should then be taken as including:
▶ named constants
▶ variable references
▶ fields in classes
▶ enumerated constants
▶ arrays
4.3 Data and Code 41
▶ records
▶ uUIConsts
▶ uUITypes
▶ uDBConsts
▶ uDBTypes
const
IniFileFolder : string = ’’;
Scope is one of the details most abused in legacy code. The use of
global variables is by now well understood to be a Bad ThingTM , but
what is often overlooked is that making public things which need
not be is equally bad. Make things private as a first determination.
Increase scope only as necessary, and keep the public interface of
each module as thin as you can.
Within each unit, you have multiple scope declarations available:
▶ private
▶ protected
▶ public
▶ published
In more recent versions, you will also see:
▶ strict private
▶ strict protected
Public members, as the name implies, are visible inside and outside
the unit.
In recent versions of Delphi a class declaration can also contain
embedded class declarations. Whether this is a better approach
than declaring separately the class that will be a member is a
decision for the developer.
options. The logic for those behaviors is specific to the form, and
should be implemented there. However, if that logic requires
complex functions, those should be separated, not written in the
form.
Data Modules are more than simply class units, and less than forms.
They stand between those levels in providing non-visual services,
yet being containers for statically committed components. They
may reasonably contain business logic, though it will normally
be present because it is related directly to the datasets which are
contained in the module.
Data modules can be tested, if the datasets are loaded, usually
from CSV or XML test data. This is highly advantageous, as actual
databases in a development shop tend to be corrupted often and
badly.
Yes, the point was made above, but it cannot be sufficiently stressed.
Narrow scope is a contributor to good design, and wide scope is an
invitation to defects, as well as to maintenance challenges.
It is not possible for literally everything to be private, but nothing
should be exposed by any module unless there is a clear reason to
do so. When you add a method or a field variable, your default
4.4 Scope in General 49
Global variables are best avoided, because they put the program ▶ CR = #13;
▶ LF = #10;
state at risk. Better by far to use Field members in classes, which
▶ CRLF = #13#10;
are distinct between class instances.
In particular, any constant which is
Global types and constants, on the other hand, raise no such issues. referenced in multiple units should
In particular, it will be better to declare a global type—and use it— be declared in only one.
4.4.7 Partitioning
If you find yourself dealing with large uses clauses, and if you have
long build times, then you should be considering that you have par-
Large uses clauses and an excessive titioning issues. These go hand in hand. Routines wrongly placed
number of Unit Dependency Cycles are lead to dependencies which ought not to exist. Getting things into
bad because they increase coupling.
Left unchecked, at some point you
the right classes and modules will lead to simplification.
will find that everything seems cou-
Also, you should note that the Unit Dependency Cycles problem
pled to everything. That puts you in
a strait-jacket. tends to increase exponentially. The exponent may be small, but
the ever increasing build times, like compound interest, will kill
you. It is not only the degraded build times, but the complexity
of code which Unit Dependency Cycles create. Such cycles are
manifestations of tight coupling, and they interfere with your
ability to separate things, as well. You will find yourself looking
for the end of the thread to pull, and it may be very difficult to
recognize. Moreover, there are likely to be many such threads to be
found. It is a tedious but essential task which is best accomplished
at the earliest opportunity. And repeatedly.
Delphi Unit Dependency Scanner can emit a CSV file of information
on the unit relations suitable for use by the open source Gephi
(https://gephi.org) tool. A graph of one of my small projects is
here:
If that were typical, there would be no need for this book. However,
here is a small fragment of edges (connections) from a large legacy
project:
Delphi has provided exception handling ever since the first release.
And yet, it is all too common to find in legacy code a variety of
offenses against exception handling logic. Consider these general
priorities for the benefits of exception handling:
▶ Prevent application crashes.
▶ Provide users with useful, comprehensible error messages.
▶ Provide the developers with useful information for defect
repair.
None of these can be fulfilled unless the exception handling of
Delphi is properly understood and used. Let me emphasize here
the value of useful, comprehensible error messages. All too often, the
messages put in front of users are of almost no value.
end ;
finally
ShowMessage ( ’In finally ’);
end ;
end ;
When you run this tiny application and click the button, the
assignment to sl.CommaText will throw an exception, as we have
not yet instantiated the object sl. Click Continue on the exception
dialog, and you will then see the message presented by the finally
clause. Execution of the first call to ShowMessage is blocked by the
exception handler; any code following an exception up to the
except clause itself, will not execute.
sl := TStringList . Create ;
try
sl . Text := AString ;
for s in sl do
begin
// ... do work here
end ;
Note that the empty finally clause
finally
is a coding failure unto itself. In this
end ; instance, because an object was not
end ; freed. But an empty finally clause
is always a code smell.
You may object that this is obviously incorrect, and so it is, but
such errors have been seen in production code, and they may lie
in your path, as well. It is easily corrected:
Another similar problem—a twin to the empty finally clause— As Nick Hodges points out in his
occurs when a call is made to some system which is assumed to first book: ”Very often, the code in
the try block raises an error that isn’t
have been initialized, but may not have been. The right action easily found, and rather than do the
would be to analyze the program flow enough to make certain it hard work of actually tracking down
is always initialized before use. The code smell here is the empty the error, the programmer will take
except clause[10]: the easy way out and just eat the ex-
ception.” I quote Nick here because
procedure TForm1 . SendIt ( EMailObject : TEmailObject ); I agree completely with his assess-
ment.
begin
[10]: Hodges (2014), Coding in Delphi
try
MyMailer . Send ( EMailObject );
except
end ;
end ;
54 4 Beginning
Not only is an empty exception han- This code simply hides the exception when it occurs. The user
dler a coding evil, but an exception is not notified, even though the expected result will not occur.
handler ought not to be coded at such
a high level—the place to catch an
This is lazy, and is often seen in connection with code where the
exception is as near as possible to its developer may not properly understand the possible errors.
source.
This is better, but not hugely so:
The problem here is that this code collects and reports any excep-
tion thrown in this scope, and in doing so, swallows the exception.
A much more useful approach is to trap the exception(s) expected
in the context of the operation:
Now if your exception handler did not find the exception you
specified, you need to handle that case, as you may have other
issues to resolve. The first thing to do is to modify your exception
handler to re-raise the exception:
raise ;
end ;
// If no exception was trapped , then any code
// here will execute .
end ;
try
slThree := TStringList . Create ;
try
// working code here
finally
slThree . Free ;
end ;
finally
slTwo . Free ;
end ;
finally
slOne . Free ;
end ;
end ;
The more objects you must create, of course, the worse this gets.
An alternative form is possible, as shown here:
finally
slThree . Free ;
slTwo . Free ;
slOne . Free ;
end ;
end ;
No need for try/finally, and yet the objects are disposed when
they go out of scope.
A critical aspect of good exception handling is to keep the scope Hopefully you will notice I keep re-
as narrow as possible. In practice this means putting the exception peating this advice.
58 4 Beginning
In many legacy projects, there will have been a long trail of different
developers, each of them leaving footprints in the code. One of
the more obvious results is an incoherent collection of formatting
styles. Arguments are offered for leaving it as it is:
▶ Reformatting makes a mess of source control.
▶ Formatting is only cosmetic, not functional.
▶ Avoids endless debates over what format is the “right” one.
▶ Reformatting offers no return on investment.
4.6 Code Formatting 59
Long routines tend to hide or ob- Well crafted code presents patterns throughout. The human mind
fuscate patterns. The clutter in such is probably the ultimate pattern recognition tool, and is much more
routines is white noise to the mind.
effective when the patterns are consistently used. At some point,
the consistency achieved becomes transparent to the developer
reading the code, and though a particular developer may be new
to that code, it will be more comprehensible for being written
consistent with the practice in other modules.
Some will argue in favor of permitting this form:
While the former may not seem objectionable, the latter has a
horrible stench. But even the former small example should be
disallowed, as it makes debugging difficult. You can break on
the test, but to break on DoThis or DoThat will require placing
breakpoints inside those routines, and if they are in other modules,
that is fairly disruptive to the debug session. This is objectively
better:
if SomeCondition then
DoThis
else
DoThat ;
for i := 0 to 9 do
ActionRoutine (i);
There are those who will argue for a slightly different approach:
Dec ( Result );
end ;
Result := MaxLen - Result ;
end ;
I am not a fan of this style, both because I find it less readable, and
because the effect on the structural highlighting of either CnPack
or Castalia is ugly. An argument often advanced in support of this
Long routines are a code smell! They style is that it takes fewer lines. Although that is true, it is a minor
should be on your priority list for gain, and if your code suffers from large routines, the benefit is
refactoring.
very small; better to refactor.
The sample is taken from Delphi XE, and might now be written
thus:
Some may also point out that the Delphi libraries do not always
follow the layouts I recommend, nor those documented by Charlie
Calvert:
It’s a fair point which still does not diminish the arguments
I advance in support of more consistent formatting. It is also
reasonable to imagine that work has been done in the libraries by
developers whose skills were rather less than those tasked with
compiler or IDE design.
Be kind to those who follow in your footsteps:
Code is read much more often than it is written, so
plan accordingly. – Raymond Chen
Formatting by Default
Working in legacy projects, you will often see useless comments. Never comment the obvious. And
These are the sort of entries which lead people to rationalize that when in doubt, try first to make the
code obvious. If that fails, then use
comments are a waste of space and effort. comments.
// Save the settings to a file
procedure SaveSettingsToFile ( AFileName : string );
begin
if not AFileName . IsEmpty then
begin
if FileExists ( AFileName ) then
DeleteFile ( AFileName );
// and so on ...
end ;
end ;
The author of that mess, almost certainly now gone from the scene,
should have been taught to keep in mind that later developers
may well be psychopathic and vengeful.
Another source of annoyance comes from explanatory comments
which have been written not by the author of the code, but by
a successor who wrongly thought he had properly understood
the operation and need of the code. Having thus planted seeds
of confusion, the guilty party has probably been rewarded by
someone who observed the output without comprehending its
future cost.
Unit Dependency Cycles and Build Time 6.4 Using the Tools . . . . 85
6.5 Unit Dependency
Regarding build time, remember: Cycles . . . . . . . . . . 87
▶ Unit Dependency Cycles do affect build time 6.6 Collecting the UDCs . 87
Note that unit dependency cycles are
▶ Your results may vary
a major factor in that project.
▶ To obtain a marked benefit, such as I did, would depend
on your project having a very large number of Unit
Although you can begin without
Dependency Cycles
refactoring, you will find as you pro-
ceed that some repairs cannot be ac-
The impact of UDCs on build time appears to be exponential,
complished without refactoring or
so although it is initially unnoticeable, once it reaches a certain rewriting. Code which was added
point, the degradation advances very noticeably. This makes into the wrong class or unit must
sense if you consider that as you add modules, and they sooner or later be transplanted.
reference others which cause UDCs, the effect is multiplicative.
If your project makes use of initialization and finalization The alternative would be to carefully
clauses in any modules, then altering uses clauses is likely to analyze all initialization actions in
the application, and establish how
trigger initialization sequence problems. With that in mind, a safe best to group them. And then deter-
sequence of actions would be: mine in what order the groups would
need to be applied. Worth doing, but
a big project.
72 6 Cleaning Uses Clauses
6.1 Initialization
type
TLogger = class
private
FLog : TStringList ;
FOutputFile : string ;
public
constructor Create ;
destructor Destroy ; override ;
procedure AddItem ( const AItem : string );
end ;
6.1 Initialization 75
var
Logger : TLogger ;
implementation
uses
Winapi . ShlObj , System . SysUtils , System . DateUtils ;
function GetSpecialFolderPath (
CSIDLFolder : Integer ): string ;
var
FilePath : array [0 .. MAX_PATH ] of char ;
begin
SHGetFolderPath (0 , CSIDLFolder , 0, 0, FilePath );
Result := FilePath ;
end ;
finalization
Logger . Free ;
end .
program DemoLogger ;
uses
Vcl . Forms ,
u_Logger in ’ u_Logger . pas ’,
fMain in ’ fMain . pas ’ { frmMain } ,
u_Files in ’ u_Files . pas ’;
{ $R *. res }
begin
Application . Initialize ;
Application . MainFormOnTaskbar := True ;
Application . CreateForm ( TfrmMain , frmMain );
Application . Run ;
end .
unit fMain ;
interface
uses
Winapi . Windows , Winapi . Messages , System . SysUtils ,
System . Variants , System . Classes , Vcl . Graphics ,
Vcl . Controls , Vcl . Forms , Vcl . Dialogs ;
type
TfrmMain = class ( TForm )
public
end ;
6.1 Initialization 77
var
frmMain : TfrmMain ;
implementation
{ $R *. dfm }
uses
u_Logger ;
initialization
Logger . AddItem ( ’ frmMain initialization ’);
finalization
Logger . AddItem ( ’ frmMain finalization ’);
end .
unit u_Files ;
interface
implementation
uses
u_Logger ;
initialization
Logger . AddItem ( ’ u_Files initialization ’);
finalization
Logger . AddItem ( ’ u_Files finalization ’);
end .
If you must add logging to many units, then you may wish to use
a conditional:
initialization
{ $IFDEF LOG_INIT }
Logger . AddItem ( ’ frmMain Initialization ’);
{ $ENDIF }
// initialization code here
78 6 Cleaning Uses Clauses
finalization
{ $IFDEF LOG_INIT }
Logger . AddItem ( ’ frmMain Finalization ’) ;
{ $ENDIF }
// finalization code here
end .
When you run the demo, it will produce a log file with content
similar to this:
Nothing too exciting, but if you are working on a project with over
100 units which contain initialization sections, this can quickly give
you a reliable knowledge of the sequence in which the initialization
occurs.
In the next round, edit each of your project units, putting the code
The names are up to you, but these from initialization into a procedure called InitializeUnit, and
seem a reasonable choice. if there was a finalization section, put that code into a procedure
called FinalizeUnit. Comment out the original initialization and
finalization sections, or remove them.
Finally add a unit to the project—InitMgr, perhaps—with two
public routines: Initialize and Finalize. In the Initialize routine,
put calls to the InitializeUnit procedures, ordered according to
the sequence you acquired by logging. In the Finalize procedure,
put the calls to FinalizeUnit, in reverse order to their sequence in
Initialize. In each case, the calls are qualified by the unit names.
All of the units involved are added to the implementation uses
clause. An example of the Initialize code is:
procedure Initialize ;
begin
MainData . InitializeUnit ;
COMInterface . InitializeUnit ;
// and so on
end ;
procedure Finalize ;
begin
6.1 Initialization 79
COMInterface . FinalizeUnit ;
MainData . FinalizeUnit ;
end ;
Build and run the tool, or run the pre-compiled executable, and
when the form opens, you will see four edit boxes at the top:
Proceed as follows:
1. Click on the dialog button for the Project Folders and navi-
gate to the root folder of your project code, then click Select
Folder.
2. Click on the dialog button for the Map File and navigate to
the map file produced in your build. Select the file, and click
Open.
3. Click on the dialog button for the DCU Folder and navigate
to the folder containing your DCU files, then click Select
Folder.
4. Click on the dialog button for the Unit Report file, navigate
The Unit Report is produced by an
to it and click Open. MMX operation. If you are unfa-
miliar with it, see the discussion of
The order of use of the buttons is seen here: MMX, below.
And finally, the list of duplicate files, if any. These would be high
on my priority list for cleanup.
checkboxes, and the one which is checked is active, while the other
can be removed.
Although this looks simple in a small project, imagine a few
thousand modules in a large project, and the value of this simple
tool is more apparent. Further, the tool aids in removal of the dead
modules:
As you can see, files may be tagged and removed. But in addition,
for the sake of a large project, you are also able to export the lists
to CSV files, for import to MS Excel .
CnPack provides the CnPack Uses Cleaner, which is quite good for
removing unneeded references. It is not perfect, and does suggest
removals from the interface uses clause which are not valid. This
causes no difficulty, as the units are added in again by the IDE on
saving the file.
Pascal Analyzer, however, is perhaps the only available tool which
will tell you which units can be moved from the interface uses
clause to the implementation uses clause. And MMX makes the
work of demoting to implementation level painless. There does not
84 6 Cleaning Uses Clauses
appear to be any tool on the market which will automate the move-
ment of unit references from the interface to the implementation
section. This is not a simple issue, and Pascal Analyzer routinely
produces false positives. In particular:
▶ Class helpers are flagged as not needed.
▶ Math and StrUtils units will be flagged as unneeded if they
are referenced only for the IfThen() functions.
▶ Unreliable in determining whether some Delphi libraries can
be removed or demoted to implementation.
▶ Unreliable in reporting unneeded component modules.
where it should have been, and reduced by nearly 800 cycles the
contribution of the unit it came from to the unit dependency cycle
total, but you will read more of refactoring later. Most gains will
be less spectacular, but you only need one such large improvement
to strengthen your determination.
Used units:
Rather than repeat the phrase endlessly, I shall refer to UDC when
discussing unit dependency cycles.
Here is the sequence of actions I recommend:
▶ Scan for and remove duplicate units. The MMX Uses Report
makes it easy to check which source files are actually in use,
which makes duplicate removal simpler.
▶ Run the MMX Unit Dependency Analyzer, and then save the
Cycles Report to a file. You may want to name it as a baseline
report, against which you can check your progress later. You
may wish to also use my simple Uses Analyzer, which will
parse the report and present cycle counts per module.
▶ Run the Peganza Pascal Analyzer tool against your project.
Save the reports to a folder for that purpose. Initially you
will use only the Uses.txt file from Pascal Analyzer, but you
will need to use it for some considerable time, depending
on the size of your project.
Open your project in the Delphi IDE. Build the project, and then
from the MMX menu, go to Tools–>Unit Dependencies.... You will
see this:
Processing a large project will take some time. Minutes, not seconds.
Be patient.
88 6 Cleaning Uses Clauses
Your project should generate no Hints and Warnings. If you have not
Yes, seriously, zero (0) Hints and achieved that, do so now. Hints and Warnings are the most basic
Warnings. level of error protection the compiler can provide. These protect us
from nasty oversights and coding errors which could be difficult
to trace in defect repair. It is surprising how many experienced
Delphi developers pay little attention to these messages.
Static code analysis with FixInsight will also save you time in the
long run. There are many trivial artifacts which should be removed
right at the outset.
Yes, bad code. It’s not a judgment call, More important, your attention will be drawn by FixInsight to
these are signs of trouble coming. empty except clauses, empty finally clauses, and other such bad
code.
7.3 Types and Constants 91
Database in Particular
Curiously, it is very common in legacy projects that field widths
in Delphi and in SQL are not centralized. This is madness. Sooner
or later you need to expand a field width—inevitably a field
which is widely accessed—and that simple change becomes a
huge task.
92 7 Simple Things
7.3.2 Unicode?
Depending on the version of Delphi you are using, and the age of
your legacy code, you may be facing issues with Unicode. The
topic has been addressed ably by others: Delphi and Unicode by
Marco Cantù:
https://www.embarcadero.com/images/old/pdf/Delphi-
Unicode181213.pdf
Delphi Unicode Migration for Mere Mortals by Cary Jensen:
https://www.embarcadero.com/images/dm/technical-
papers/delphi-unicode-migration.pdf
Delphi in a Unicode World by Nick Hodges:
https://www.embarcadero.com/images/dm/technical-
papers/delphi-in-a-unicode-world-updated.pdf
The Absolute Minimum Every Software Developer Absolutely, Positively
Must Know About Unicode and Character Sets (No Excuses!) by Joel
Spolsky: https://www.joelonsoftware.com/2003/10/08/the-
absolute-minimum-every-software-developer-absolutely-
positively-must-know-about-unicode-and-character-sets-
no-excuses/
7.3 Types and Constants 93
Deep in the history of Pascal we were given some less than obvious
numeric type declarations. Happily, Delphi has long provided
aliases for them, and these are highly recommended, being in the
nature of self-documenting:
type
Int8 = ShortInt ;
Int16 = SmallInt ;
94 7 Simple Things
Int32 = Integer ;
IntPtr = NativeInt ;
UInt8 = Byte ;
UInt16 = Word ;
UInt32 = Cardinal ;
UIntPtr = NativeUInt ;
Some types of this form are not aliases, and are declared exter-
nally:
type
Int64 = __int64 ;
UInt64 = unsigned __int64 ;
type
TIntArray10 = array [0..9] of integer ;
var
arrA : TIntArray10 ;
arrB : TIntArray10 ;
begin
arrA := arrB ; // works fine
end ;
Rather than:
var
arrA : array [0..9] of integer ;
arrB : array [0..9] of integer ;
begin
arrA := arrB ; // E2008 Incompatible types
end ;
type
TIntArray = array [0..0] of Integer ; // bad choice
// Delphi XE has , in SConnect :
type TIntArray = array [0..0] of Integer ;
96 7 Simple Things
type
TwmIntArray = array [0..0] of Integer ;
TwmStrArray = array [0..0] of string ;
type
TAlignment =
( taLeftJustify , taRightJustify , taCenter );
type
TLeftRight =
TAlignment . taLeftJustify ..
TAlignment . taRightJustify ;
type
TWeekdays =
( wdMon =1 , wdTue , wdWed , wdThu , wdFri ,
wdSat , wdSun );
TSuits =
( Spades = 1, Diamonds = 3, Hearts = 5,
Clubs = 7) ;
begin
WriteLn ( Integer ( Foo ));
WriteLn ( Integer (A)); // TFoo .A
WriteLn ( Integer ( TBar .B));
WriteLn ( Integer ( TBar . Bar ));
WriteLn ( Integer ( Bar )); // Error
end ;
Delphi also contains sets as first class members, unlike many other
languages. We can test for membership, as well as conjunction,
7.3 Types and Constants 99
disjunction, and other set properties. The use of sets often allows us
to simplify our coding without any sacrifice in logical completeness.
Consider:
type
TSuits =
( Spades = 1, Diamonds = 3, Hearts = 5,
Clubs = 7) ;
type
TSuits =
( Spades = 1, Diamonds = 3, Hearts = 5,
Clubs = 7) ;
TSuitSet = set of TSuits ;
type
TIntTen = 1..10;
var
idx : TIntTen ;
begin
idx := 10; // compiles properly
idx := 11; // compile time error here
end ;
Ranges can be very useful when you are dealing with calculated
indices, for example. If the possible values are limited, then declar-
100 7 Simple Things
ing a subrange to check validity can save you some hard to find
errors. The range checking, however, is a compiler feature, so if
you have a variable of your subrange type, and you enable range
checking, then at run time an error will be thrown if the value is
incompatible with the range.
When you need a variable to retain its last value between calls,
there are better ways. Inside a class you may wish to add a field
variable, for example. If the value is to be known to multiple
instances of the class, then declare a class field variable.
Safety First!
You may also wish to include the name of the routine where
the exception is raised. That will be less important if you use a
tool such as EurekaLog.
7.5 Forms
Delphi can autocreate forms for you when you run your application.
This feature is enabled by default in new installations. You will
probably want to disable it.
Manually instantiating a form is not unlike the notion of lazy
initialization: You don’t create a resource until you need it. In small
applications, the difference is negligible, but in a large application
with dozens of forms, autocreate is a bit silly:
104 7 Simple Things
▶ Wastes memory
▶ Slows down your application start
▶ Instills bad habits
When you create a new form, Delphi puts in the code a variable to
contain an instance of that form. This may be a good thing, but
if you need to instantiate more than one copy of the form object,
then you will need other variables to contain them. In that case,
you may find it better to remove the variable from the form unit,
as the variables used to contain independent instances should
logically be declared where they will be managed.
I wrote managed—not used—to draw attention to the fact that the
life of these instances must be managed. The mechanics of that
are up to you, but the task must be handled.
We’ve all probably seen many compiler version include files, most
of which resemble this one from ZipForge:
{ $IFDEF WIN32 }
{ $DEFINE D2_OR_HIGHER }
{ $ENDIF }
{ $IFNDEF VER80 }
{ $IFNDEF VER90 }
{ $IFNDEF VER93 }
{ $IFNDEF VER100 }
{ $IFNDEF VER110 }
{ $IFNDEF VER120 }
{ $IFNDEF VER125 }
{ $IFNDEF VER130 }
{ $DEFINE Unknown_Version }
{ $DEFINE BD5 }
{ $ELSE }
{ $DEFINE BD5 }
{ $DEFINE DELPHI_COMPILER }
{ $ENDIF }
{ $DEFINE BCB4_OR_HIGHER }
{ $ELSE }
{ $DEFINE BCB4 }
{ $DEFINE BCB_COMPILER }
{ $ENDIF }
{ $DEFINE D4_OR_HIGHER }
{ $ELSE }
{ $DEFINE BD4 }
{ $DEFINE DELPHI_COMPILER }
{ $ENDIF }
{ $DEFINE BCB3_OR_HIGHER }
{ $ELSE }
{ $DEFINE BCB3 }
{ $DEFINE BCB_COMPILER }
{ $ENDIF }
{ $DEFINE D3_OR_HIGHER }
{ $ELSE }
{ $DEFINE BD3 }
{ $DEFINE DELPHI_COMPILER }
{ $ENDIF }
{ $DEFINE BCB1_OR_HIGHER }
{ $ELSE }
{ $DEFINE BCB1 }
106 7 Simple Things
{ $DEFINE BCB_COMPILER }
{ $ENDIF }
{ $DEFINE D2_OR_HIGHER }
{ $ELSE }
{ $DEFINE BD2 }
{ $DEFINE DELPHI_COMPILER }
{ $ENDIF }
{ $DEFINE D1_OR_HIGHER }
{ $ELSE }
{ $DEFINE BD1 }
{ $DEFINE DELPHI_COMPILER }
{ $ENDIF }
Actually, that one is old and pretty clean. Small, too. Most are
worse. But it does share with most others the checking for specific
compiler versions. One difficulty with this is the need for a decoder
ring—unless you are inclined to memorize VER185 and similar, you
will surely need one. The official one is: http://docwiki.embarc
adero.com/RADStudio/Sydney/en/Compiler_Versions
Just this morning, I learned of another approach written by
Fr0sT-Brutal: https://github.com/Fr0sT-Brutal. As the au-
thor says:
With these files you can write code that is compatible
with older Delphi compilers or even other Pascal com-
pilers (FreePascal) by simply enclosing modern/spe-
cific constructions with appropriate define. Moreover
you don’t have to check compiler version where a
required feature first appeared, you just check the
availability of this feature.
Some examples are given, and you may find this as appealing as I
do.
// DON ’T:
var
s: { IFDEF UNICODE } UnicodeString
{ $ELSE } WideString { $IFEND };
// DO :
var
s: { IF DECLARED ( UnicodeString )}
UnicodeString { $ELSE } WideString { $IFEND };
7.6 Delphi Versions 107
There still is no free lunch; the need for the familiar pattern
remains:
// than
{ $IFDEF CompilerVersion >= 22 }
And for those who must maintain the code, this will be a great
value, as well.
Not so Simple Things 8
There are many issues which can arise in legacy code which are 8.1 Persistence in Settings 111
not so simple to resolve, and where possible improvements may 8.1.1 Persistence with Enu-
aggravate other issues of concern. In approaching these, you may merations . . . . . . . 111
find you need to make some notes and then let them sit. Or perhaps 8.2 Dangers in Use of
sketch out multiple approaches, and then look for their strengths ClassName . . . . . . 115
and weaknesses. 8.2.1 Decoupling with a
Dictionary . . . . . . . 116
8.3 Reviewing Code . . . 123
8.1 Persistence in Settings
unit EnumStrings ;
interface
uses
System . TypInfo ;
type
TEnumRec <T: record > = record
private
class function ToInteger (
aValue : T): integer ; overload ; static ;
function ToString : string ; overload ;
public
class operator Implicit (
aValue : integer ): TEnumRec <T >;
class operator Implicit (
aValue : TEnumRec <T >) : integer ;
class operator Implicit (
aValue : TEnumRec <T >) : string ;
class operator Implicit (
aValue : T): TEnumRec <T >;
class operator Implicit (
const aValue : string ): TEnumRec <T >;
8.1 Persistence in Settings 113
public
case integer of
0: ( Value : T);
1: ( IntValue : Int8 );
end ;
implementation
uses
System . Rtti ;
end .
Keep in mind:
▶ This is a pretty wrapper around existing functionality from
Delphi libraries. Nothing here is either exotic, though it may
not be familiar to you.
▶ This code is dependent on generics and on records with
methods. It should work on Delphi 2010 or later.
As to usage, it is pretty simple:
type
TestEnum = { eOne , eTwo , eThree };
procedure TestConv ;
var
EnumRec : TEnumRec ;
s: string ;
e: TestEnum ;
n: Integer ;
begin
EnumRec := eOne ;
s := EnumRec ;
e := EnumRec ;
n := EnumRec ;
end ;
You may think that the usage makes this resemble a variant, and
it does, and you may not find this as clear as you would like. But
8.2 Dangers in Use of ClassName 115
EnumRec := eTwo ;
s := StrFromEnum ( EnumRec );
n := IntFromEnum ( EnumRec );
// or perhaps
s := StrFromEnum ( eThree );
var
SomeClass : TSomeClass ;
begin
...
if SomeClass . ClassName = ’ TSomeClass ’ then
...
end ;
The reason for the warning is very simple, and obvious if you
consider it for a time. First, bear in mind that in practice, the class
declaration is not staring you in the face, as it is in the example. If
you have something like this:
procedure UpdateLabels ;
begin
if Self . Owner . ClassName = ’ TShinyForm ’ then
chkOption . Caption := ’Do This ’
else if Self . Owner . ClassName = ’ TDullForm ’ then
chkOption . Caption := ’Do That ’;
116 8 Not so Simple Things
end ;
Now a year or two later, and for some very good reason, TDullForm
is renamed to TSatinForm. The UpdateLabels routine now fails
to update the label on chkOption. You will need to search your
thousands of modules to see where that property is written, and
apply a fix. Not difficult, merely annoying.
The point is that there is a liability here which exists because re-
naming the form does not provoke a warning from the compiler.
A better approach would be:
procedure UpdateLabels ;
begin
if Self . Owner is TShinyForm then
chkOption . Caption := ’Do This ’
else if Self . Owner is TDullForm then
chkOption . Caption := ’Do That ’;
end ;
To begin, we will look at the dictionary. If you have not used one, it
is a generic collection which contains keys and values. In this case,
the key will be either the class name or a GUID. There is no need to
support this dual approach, but as we offer this as a replacement
for the class name testing, showing the two approaches may help
to illustrate the mechanism. Here is the class declaration:
type
TClassDict = class ( TObject )
private
FDictionary : TDictionary < string , TClass >;
public
constructor Create ;
destructor Destroy ; override ;
procedure AddItem ( const AKey : string ;
AItem : TClass );
function FoundItem (
const AKey : string ): Boolean ;
function GetClass ( const AKey : string ): TClass ;
function GetClassName (
const AKey : string ): string ;
end ;
unit u_ClassOne ;
interface
uses
u_GuidAttr ;
type
[ TGuidAttribute (
118 8 Not so Simple Things
procedure InitializeUnit ;
implementation
uses
u_ClassDict ;
procedure InitializeUnit ;
begin
ClassDict . AddItem ( ’ tclassone ’, TClassOne );
ClassDict . AddItem ( GetGuidAttribute ( TClassOne ) ,
TClassOne );
end ;
end .
unit u_GuidAttr ;
interface
uses
System . TypInfo ;
type
TGuidAttribute = class ( TCustomAttribute )
public
FValue : String ;
implementation
uses
System . Rtti ;
custom processing }
for LAttr in LType . GetAttributes () do
if LAttr is TGuidAttribute then
Result := TGuidAttribute ( LAttr ). FValue ;
end .
An attribute is a class which inherits The implementation and use of attributes will not be presented
from TCustomAttribute and must here, and there are numerous references you can consult to ac-
be implemented as any other class.
quaint yourself with them if necessary. See the Embarcadero
DocWiki help: https://docwiki.embarcadero.com/RADStu
dio/Sydney/en/Attributes_(RTTI) You will also want to
read the article on Extracting Attributes at Run Time: https:
//docwiki.embarcadero.com/RADStudio/Tokyo/en/Extractin
g_Attributes_at_Run_Time.
For now, accept that this small class works, and let us again look
at how it is used.
type
[ TGuidAttribute (
’ {942 D85C8 -9475 -4 DC8 - BEF0 - D4DEA73D0C73 } ’)]
TClassOne = class ( TObject )
end ;
procedure InitializeUnit ;
begin
ClassDict . AddItem ( TClassOne . ClassName . ToLower ,
TClassOne );
ClassDict . AddItem ( GetGuidAttribute ( TClassOne ) ,
TClassOne );
end ;
Not very illuminating, but the point is that we must first initialize
the dictionary class, then call InitializeUnit in each of the client
classes to perform the initialization. Where you choose to do this
is up to you, but for this simple demo, I put it in the application
FormCreate.
’ by Guid ’);
end ;
You may be wondering how using a GUID is any better than the
string constant for the class name. After all, both are just strings.
That’s true, but the string constant for the class name is not likely
to be seen as anything special by most developers. Worse, it is off
in some corner of the application, used on one or two modules,
and gets little attention. Worse yet, the class name itself could be
changed. We are talking legacy projects, after all, and we have
certainly seen poorly named classes. Improving such things is an
ordinary part of code cleanup. Moreover, we know that when we
alter the class name, the compiler will tell us of references we
break in the process. But string constants enjoy no such scrutiny
by the compiler.
A GUID likewise is not checked by the compiler against any
standard. However, no developer with any experience is going to
alter a GUID, as it is recognized as a constant which is critical to
some aspect of the application. So although a GUID is just a string,
it is treated with respect, and as it has no meaning obvious to a
developer, will not be altered for readability.
8.3 Reviewing Code 123
var
error : Integer ;
begin
// Turn off I/O error checking
{$I -}
MkDir ( ’ MyDir ’);
// Was it created ?
error := IOResult ;
if error = 0 then
ShowMessage ( ’ Created OK ’)
else
ShowMessageFmt ( ’ Creation failed : error %d ’,
[ error ]) ;
// Delete the directory to tidy up
RmDir ( ’ MyDir ’);
// Turn I/O checking on
{ $I +}
end ;
var
error : Integer ;
begin
{ $IOCHECKS OFF }
CreateDir ( ’ MyDir ’);
error := IOResult ;
if error = 0 then
ShowMessage ( ’ Created OK ’)
else
9.1 Local Variables 127
It’s nice that the original coder considered that we might forget
what the short form stands for, but would have been nicer still had
the long—self documenting—form been used. We should always
seek to make code explain itself, and add comments only when
there is a pressing need. In general, line comments are much less
useful than block comments, as the latter should be used to explain
why a routine exists, and why and how it does what it does.
{ $IFOPT Q+}
{ $DEFINE OVERFLOW_ON }
{$Q -}
{ $ELSE }
{ $UNDEF OVERFLOW_ON }
{ $ENDIF }
//
// Your code here
//
{ $IFDEF OVERFLOW_ON }
{ $Q +}
{ $UNDEF OVERFLOW_ON }
{ $ENDIF }
{ $IFOPT Q+}
{ $DEFINE OVERFLOW_ON }
{ $OVERFLOWCHECKS OFF }
{ $ELSE }
{ $UNDEF OVERFLOW_ON }
{ $ENDIF }
//
// Your code here
//
{ $IFDEF OVERFLOW_ON }
{ $OVERFLOWCHECKS ON }
{ $UNDEF OVERFLOW_ON }
{ $ENDIF }
The problem being that IFOPT is designed to work with the short
form symbols and a + or - symbol.
Despite the advice about performance effects of overflow checking,
9.1 Local Variables 129
Why worry over the names of local variables? When your routine
is 1,000 lines of ugly code, “local” has no meaning. When you
have 70+ local variables, each one needs to communicate clearly
why it is used. When you begin refactoring, remember to move
130 9 Cleaning Legacy Code
var
yearIdx , monthIdx , dayIdx : Integer ;
var
i , j , k: Integer ;
There is a place for short local variable names, certainly. But use
them with care, and for any variable—even local—which cannot
be overwritten on a whim, avoid them. It is perfectly reasonable
to use s as a string variable name, if:
▶ The variable is used as a short-term temporary.
▶ S need not remain unchanged over more than a very few
lines of code.
▶ The variable name does not affect readability.
var
s: string ;
begin
// noisy approach
s := Trim ( AString );
UpdateWithStr (s);
// direct , and better
UpdateWithStr ( Trim ( AString ));
// ... more code here , but no further use of s
end ;
In this example, there is still only one reference, but the use of the
local variable contributes to readability:
var
s: string ;
begin
s :=
BigTable . FieldByName ( ’ LastName ’). AsString + ’,’
+ BigTable . FieldByName ( ’ FirstName ’). AsString ;
LocalTable . Edit ;
LocalTable . FieldByName ( ’ FullName ’). AsString := s;
LocalTable . Post ;
// ... more code here
end ;
Often you may find that someone with an aversion to typing has
created a routine like this:
if SomeCondition then
DoSomeAction ;
// And , watch for this :
if SomeCondition then
Result := True ;
// which should have been :
Result := SomeCondition ;
begin
// one possibility
if ConditionA then
begin
end
else if ConditionB then
begin
end
else
begin
end ;
// and another
if ConditionA then begin
end else if ConditionB then begin
end else begin
end ;
// and a third
if ConditionA then begin
end
else if ConditionB then begin
end
else begin
end ;
end ;
All three layouts represent the same logic, but they are not equally
readable, in my view. Pattern recognition is an essential part of
development work, and the more consistently the patterns are
employed, the more value they confer on the project. Some patterns
are idiomatic:
sl := TStringList . Create ;
try
// do work here
finally
sl . Free ;
end ;
// here there is some confusion
sl := TStringList . Create ;
try
// do work here
finally
sl . Free ;
sl2 . Free ; // should not happen here
// now we search to find where sl2 was created
end ;
Forms may have options, settings, features which put the form into
some state, and which should be persisted. Users now take such
things for granted, and consider them essential behaviors, rather
than features. As features are added, and an application evolves,
the options and other stateful data tend to increase. Sometimes a
developer may add separate routines in support of persistence, as
well as restoration of state on loading.
It’s good not to let a single routine grow endlessly, but it is even
better to make sure that:
▶ Consistent naming of such routines is practiced, so that
developers recognize that identifiers like:
• UpdateSomeState
• PersistSomeState and
• RestoreSomeState
are all related and involved in such operations.
▶ A single routine should normally be used on form loading,
where it can be called form the form’s OnCreate event handler.
Good practices will make it easier to be sure that such routines
are not overlooked, and that all members of the team recognize
the idiom such routines represent.
In dealing with form state, keep in mind that there are components
available from several vendors which simplify the persistence
of form state. By default, they all, I think, persist for size and
position, but generally also have events which may be used to
add functionality you may require. Also, most will have been
designed to use either the registry or an INI file for persistence,
but in modern applications, you may find that you need to persist
to a table in a database, so that the persisted items follow the user
login, not the physical machine.
9.5 Form vs. Code Dependency 137
type
TSomeOptions = ( soDefault , soVerbose , soAbbrev );
const
SOptions = ’ Default , Verbose , Abbreviated ’;
var
oldCount : Integer ;
begin
oldCount := Options . Items . Count ;
Options . Items . CommaText := SOptions ;
Assert ( oldCount = Options . Items . Count ,
’ Options different than on form ! ’);
end ;
Again, with only three items, this may seem a lot of work, but
with a larger collection, it will more than repay your efforts. The
assignment to Options.Items will be done in the form creation.
The declaration of the enumeration, the Items strings, and the
initialization can all be in your new business logic unit:
unit SomeFormBusinessLogic ;
interface
type
TSomeOptions = ( soDefault , soVerbose , soAbbrev );
TFormLogic = class
public
procedure InitOptions ( AOptions : TRadioGroup );
end ;
implementation
const
SOptions = ’ Default , Verbose , Abbreviated ’;
end .
But that is simply a bandage, and a leaky one, at that; the root
problems remain. A better approach would be similar to that for
the RadioGroup, above. Again, hard-coded values will become
problems in the future. There is a tendency for ComboBox lists to
change, with items added, removed, or reordered. Assigning an
index as though it would always be correct is terrible practice.
unit SomeFormBusinessLogic ;
interface
type
TListIndices = ( soDefault , soVerbose , soAbbrev );
140 9 Cleaning Legacy Code
TFormLogic = class
public
procedure InitList ( AOptions : TComboBox );
end ;
implementation
const
SOptions = ’ Default , Verbose , Abbreviated ’;
end .
Note that you could make broader use of the TListIndices enu-
meration:
type
TDataIndices = ( soItem , soDate , soName , soStreet ,
soCity , soState , soZip );
const
DataFields : array [ TDataIndices ] of string = (
’ RecID ’, ’ LastEdit ’, ’ FullName ’, ’ Street ’,
’ City ’, ’ State ’, ’ Zip ’);
procedure ProcessFields ;
begin
9.6 Types & Consts Again 141
for idx := Ord ( soItem ) to Ord ( soZip ) do This code uses FieldByName in pref-
erence to Fields[idx]. Unless you
begin
have a severe performance issue to
s := SomeData
resolve, the use of FieldByName is
. FieldByName ( DataFields [ idx ]) . AsString ; often desirable because it frees you
ApplyTheStr ( idx , s); from the concern of column ordering.
end ;
end ;
type
TDataIndices = ( soItem , soDate , soName , soStreet ,
soCity , soState , soZip );
TFldInfoRec = record
fld : string ;
cap : string ;
end ;
const
DataFlds : array [ TDataIndices ] of TFldInfoRec = (
( fld : ’ RecID ’; cap : ’ Item No . ’) ,
( fld : ’ LastEdit ’; cap : ’ Updated ’) ,
( fld : ’ FullName ’; cap : ’ Name ’) ,
( fld : ’ Street ’; cap : ’ Street ’) ,
( fld : ’ City ’; cap : ’ City ’) ,
( fld : ’ State ’; cap : ’ State ’) ,
( fld : ’ Zip ’; cap : ’ Zip Code ’) );
procedure ProcessFields ;
var
fldName : string ;
begin
for idx := Ord ( soItem ) to Ord ( soZip ) do
begin
fldName := DataFlds [ idx ]. fld ;
s := SomeData . FieldByName ( fldName ). AsString ;
ApplyTheStr ( idx , s , DataFlds [ idx ]. cap );
end ;
end ;
The point is that a great deal can be done with types and constants
to resolve things which legacy code often addresses through hard-
coded approaches. This is certainly true for many things which
can be fully resolved at compile time.
142 9 Cleaning Legacy Code
9.6.1 Doppelgangers
The first edition of the Delphi Component Writer’s Guide tried hard to
drive home the principle that a component must be ”contract free”,
a phrase which I do not see in the version of Component Writer’s
Guide which shipped with Delphi 7. Perhaps it was considered
insufficiently clear. In simple terms, that means that you drop
it on a form and can immediately interact with the published
properties, to determine details of the component’s behaviors. For
example, place a TLabel on your form and you can then assign the
Caption, and the Font properties, such as size and color.
The notion of ”contract free” becomes more complicated when
components are linked together, but one thing is certain: when
linked, interactions with the Object Inspector should not cause
any exception to be thrown.
If you have not written any components, I urge you to read the Do not overlook the old but still ex-
Component Writer’s Guide first, and start with simple things. I cellent Visual Developer Developing
Custom Delphi 3 Components, by Ray
remember struggling with the first edition, and the one from Konopka. He goes more deeply into
Delphi 7 is much more comprehensible. But then, my reading the practice of component design,
it now is influenced by 25 years of work in Delphi, so it may and the book should be required
simply be a change in my perception. At any event, the Component reading for anyone who seriously in-
tends more than dabbling in compo-
nent design.
146 10 Local Components
Changing Perspective
inherited Loaded ;
try
if FStreamedConnected then
Open { reestablish connections }
else
CheckSessionName ( False );
except
if csDesigning in ComponentState then
{ at design time ... }
{ let Delphi handle the exception }
Application . HandleException ( Self )
else
raise ; { otherwise , reraise }
end ;
end ;
Correctly handling an exception depends on recognizing the dif- If you are unable correctly to handle
ference between design time and run time. At run time, you will an exception in your try/except cod-
ing, do not swallow it. Raise it again,
ensure that an exception is raised. The component user must un- so it is not lost.
derstand that a component can throw an exception, and therefore
provide suitable coverage in the application code.
Independence of Components
Third-party components do not A well designed component should be able to provide its
make use of your application code. functionality in any project. If you borrow application code in
Neither should your own components do
so.
building a component, you make that component less indepen-
dent, and less reliable. A developer should never have to fear
that a component will cease working because of some repair
made in application code.
Sometimes it may be difficult to square this with the notion
of not duplicating code. Ask yourself why the component is
so specific to the application. Could you perhaps split the
component into more than one, and what would have been
duplicated code becomes instead a component that can provide
the needed behavior in your application?
Design should always precede coding.
code that functions have been written which could now be replaced
Using library functions does not by—or at least simplified by—using library functions. You may
mean you will never encounter er- find the remaining code is a modest percentage of what was there
rors; Delphi has defects, even as any
other non-trivial software applica-
before, and library functions have been well tested before you
tion. received them.
After culling the functions which were obviated by library code,
the remaining functions should be separated out to new modules.
Keeping things clean means keeping the scope in these modules
as narrow as possible.
▶ Freely use Delphi library modules, as needed.
▶ Keep the interface uses clause as small as possible; prefer
putting references into the implementation uses section.
▶ Reject making use of existing application modules.
Fair warning: Delphi library modules are generally very solid, but
they are not bug-free. Using them does not exempt you from the
need for unit testing, but they do reduce the likelihood of defects.
Consider the example of DateUtils. In recent versions, Delphi
DateUtils code has been reworked to fully support the ISO-8601
standard. You reap the benefits without doing the research or the
coding. Even if you need specialized routines for handling dates,
you will do well to implement those on top of the Delphi library.
The other thing about keeping uses clauses small is that the need
for many references suggests that the class in the module is doing
too many things. Remember the Single Responsibility Principle.
Keep your classes well focused, and you will not need huge uses
clauses.
Sage Advice
You can’t reduce the old problems by repeating the old prac-
tices.
When you have created new modules, you will also want to
remove the old functions from the modules in which they had
been written. The application code can make use of the new
modules, instead. But how is this better than before? And is this
not again intermingling?
In creating new modules, you need to consider how to make them
as clean as a Delphi library module. If you are able to do that,
then locate them in a new folder which makes clear that they are,
indeed, libraries. You and your colleagues will need to keep in
mind stricter rules for these new modules:
▶ Ruthlessly avoid the creation of UDCs.
▶ Build comprehensive unit tests for the new modules, and
diligently maintain them.
154 10 Local Components
As Fowler wrote an entire book on the subject[5], I will not attempt [5]: Fowler (1999), Refactoring
to offer comparable coverage here. There are several key points,
however, which should be kept in mind:
▶ Always leave a unit better than you found it.
156 11 Refactoring
internal states of the routine. These are ripe for refactoring, and
with very low risk. A modest bit of editing leaves you with a well
named call in place of the forty or more lines which have been
Remember that your nested routines factored out. This amounts to an iterative analysis of functionality,
should also be kept as short as possi- from the inside out. As the outer routine is reduced—and lines
ble.
and lines of code are replaced by well-named calls—your ability
to comprehend what it really is doing is increased.
The nested routines may, in some cases, be better placed in a class.
If they are not useful other than in the present class, then define
the class in the implementation section of the current unit. As
work proceeds, it may become apparent that one or more other
modules contain similar code, and at that point, the class should
be moved to a separate unit.
Always, in such refactoring, keep in mind the long term goal of
testable code.
▶ Nested routines are better than inline code.
▶ A private class is better than nested routines.
▶ A (testable) class unit is better than a private class.
▶ Smaller routines are easier to write, test, understand, and
use.
that can reasonably find use in more than one module. Stay fo- A class should do one thing really
cused. Keep your code focused. You may elect to use one of the well. It should not yield to the creeping
features syndrome. See SOLID (Chap-
well-known approaches to Separation of Concerns (section 21.4), ter 22.)
whether MVC, MVP, or other framework, or you may simply main-
tain minimal code in your forms, business logic in testable units,
and data handling in data modules. Whatever your strategy:
▶ Keep scope narrow.
▶ Keep forms as dumb as possible.
▶ Put business logic into well-designed classes in their own
units.
▶ Put data handling into data modules.
Ideally, you will want separation between your form and its
business logic. The data module should not be the repository
for all business logic, but only for that portion which is directly
involved with data manipulation. The public interface between
the data module and the form(s) it serves should be as small as
possible.
The business logic which is not tightly linked to database opera-
tions should be in one or more separate units. For a major form,
begin with the form, a data module, and a unit containing business
logic. The form and the business logic unit should have similar
names, so their relationship is obvious. Naming the data module(s)
may be less simple, as there may well be multiple forms served by
a data module. This separation of concerns (section 21.4) should
serve to guide your approach in new work.
11.4 The Challenge of Testing 161
There are many ways to judge the size of a routine, and one
good one is that if you can’t read all of it without scrolling, it’s
too big. Often I have worked on routines which are hundreds of
lines long. If everything were well named, they would still be
incomprehensible, but it is axiomatic that these monsters also are
peppered with meaningless names. My approach varies somewhat,
based on what I find, and what brought me there. But there are
common actions:
▶ Factor out blocks of assignments.
▶ On multi-level loops, begin at the inside, factor out nested
Keep in mind that most people find it
much easier to comprehend the effect routines with good names.
of positive logic than of negative. If ▶ Apply logic reduction. Simplify, always simplify.
you must use negation, be creative in ▶ Where actions are dependent on state, group as possible.
making it comprehensible. Adding
▶ Create private classes, where appropriate.
partial terms may help.
You cannot fully understand huge routines.
11.5.1 Resources
Others have written better than I could about the process.[4, 5] [4]: Feathers (2004), Working Effec-
The seminal works are in the side margin of this page. They will tively with Legacy Code
[5]: Fowler (1999), Refactoring
richly reward the time you invest. They are worth repeated study,
and will change the way you code.
Before you write any new code, you should consider whether the
needed functions are already available in library code sources, in
this order of preference.
1. Delphi libraries
2. Trusted third-party libraries or components
3. In-house unit tested libraries
4. In-house libraries, not unit tested
There are costs to any coding activity, and not merely for de-
You may have fundamentally criti-
velopment, but for documentation, testing, and maintenance. It cal routines which depend on library
will always be cheaper to make use of code you do not have to routines, and are concerned about
document or test. the risk of library correctness. You
can certainly create unit tests for li-
The Delphi libraries have evolved over a quarter century and have brary code, but will probably wish
been heavily tested. Some of that code had also come from Turbo to limit the testing to the specific rou-
tines you find critical. Should any er-
Pascal, so has an even longer history. Though they are generally rors be reported, you will then need
reliable, they have occasionally been found to contain defects. Still, to dig more deeply, but the unit tests
they are probably the most widely used Delphi code available, and will have raised a proper warning.
That said, you will need to ensure the
you should make good use of them wherever possible.
absolute correctness of your tests.
Trusted third-party libraries are as beneficial as the Delphi libraries,
but there is that qualifier: trusted. Among the third-party com-
ponents and libraries are products which range from excellent to
166 11 Refactoring
very poor. As the saying goes, trust, but verify. If you have cause
to doubt the reliability of any third-party code, you will need
either to replace it, or heavily test it. Unit testing, if the coverage
of test cases is thorough, can raise such modules to trusted status.
Remember:
▶ Commercial software is not always great.
▶ Free and open source code may be better than commercial.
Or not. Make no assumptions. Trust but verify.
▶ Tests always trump trust.
interface
uses
System . Classes ;
type
IDelimitedText = interface ( IInterface )
[ ’{ FB67C93C -8779 -4 F51 - A257 -37 BC89C4A813 } ’]
procedure Add ( const S: string );
procedure Clear ;
procedure Delete ( const Index : Integer );
function Find ( const S: string ;
var Index : Integer ): Boolean ;
function GetCommaText : string ;
function GetCount : Integer ;
function GetDelimitedText : string ;
function GetDelimiter : Char ;
function GetSorted : Boolean ;
function GetStrictDelimiter : Boolean ;
function GetStrings ( Index : Integer ): string ;
function GetText : string ;
procedure IgnoreDupes ( const State : Boolean );
function IndexOf ( const S: string ): Integer ;
procedure PutStrings ( Index : Integer ;
11.9 Prefer Composition 169
Some of the members are not strictly needed, but were added to
make the class more widely useful. That is a slippery slope, and if
you are too quick to add features, you end up with something as
complex as the TStringList with which the work started. Note in
particular the function at the end of the listing. There are many
ways in which you might design an interfaced class, but as my
purpose here was specifically to restrict the public interface, I
elected to define the interface in public, and the implementation
privately in the implementation section of the same unit. The
function is needed then to instantiate the class. You may prefer to
define the interface in a separate module, and perhaps the module
will contain a collection of interface declarations. This is not a
170 11 Refactoring
implementation
uses
System . StrUtils ;
type
TDelimitedText = class ( TInterfacedObject ,
IDelimitedText )
private
FStringList : TStringList ;
function GetCommaText : string ;
function GetCount : Integer ;
function GetDelimitedText : string ;
function GetDelimiter : Char ;
function GetSorted : Boolean ;
function GetStrictDelimiter : Boolean ;
function GetStrings ( Index : Integer ): string ;
function GetText : string ;
procedure PutStrings ( Index : Integer ;
const Value : string );
procedure SetDelimitedText (
const Value : string );
procedure SetDelimiter ( const Value : Char );
procedure SetSorted ( const Value : Boolean );
procedure SetStrictDelimiter (
const Value : Boolean );
procedure SetText ( const Value : string );
public
constructor Create ;
destructor Destroy ; override ;
procedure Add ( const S: string );
procedure Clear ;
procedure Delete ( const Index : Integer );
function Find ( const S: string ;
var Index : Integer ): Boolean ;
procedure IgnoreDupes ( const State : Boolean );
function IndexOf ( const S: string ): Integer ;
property CommaText : string read GetCommaText ;
property Count : Integer read GetCount ;
property DelimitedText : string
read GetDelimitedText
write SetDelimitedText ;
property Delimiter : Char
11.9 Prefer Composition 171
read GetDelimiter
write SetDelimiter ;
property Sorted : Boolean
read GetSorted
write SetSorted ;
property StrictDelimiter : Boolean
read GetStrictDelimiter
write SetStrictDelimiter ;
property Strings [ Index : Integer ]: string
read GetStrings
write PutStrings ; default ;
property Text : string
read GetText
write SetText ;
end ;
Interface History
function TDelimitedText
. GetStrictDelimiter : Boolean ;
begin
Result := FStringList . StrictDelimiter ;
end ;
In this case, the delimited lists were to be passed across a COM in-
terface, so it was imperative that the conversion between string and The isolation afforded by properties
list be reliable and unchanging. Making a TStringList a member leaves open the possibility of a com-
pletely new approach to implemen-
of the class hid the plethora of members of that class, and allowed tation in the future. I might decide to
me to be selective in what would be exposed. Moreover, in moving eliminate the use of TStringList in
the code to the latest Delphi compiler, the internal implementation favor of lighter-weight alternatives.
may change radically, and eliminate the TStringList altogether, in
favor of more efficient code from the newer Delphi libraries.
One of the benefits of properties in Delphi is that they hide from
the consumer the implementation, so it is not apparent in the call
whether it is a simple field access or a method call. Similarly, using
composition allows the implementation to be hidden from view,
and the public interface may remain constant, even as the private
implementation is altered. These are among the key principles
which make OOP an attractive methodology.
Removing Code from Forms 12
Code on forms is not easily unit tested. It is possible, however, 12.1 Extract Utility Rou-
to remove so much from the form that the data handling and tines . . . . . . . . . 177
business logic may be fully tested, leaving the form testing as a 12.1.1 Refactoring Praxis . 178
much simpler exercise which ensures no more than the proper 12.2 Building Utility
logical interaction of the controls. When business logic and data Units . . . . . . . . . 179
handling cannot be unit tested, then GUI testing is generally used 12.3 Shared Classes . . . 180
as a fallback, but there are issues: 12.4 Use Data Modules . 181
▶ Access to business logic is indirect; it is difficult to be sure 12.4.1 Common Operations 181
all behaviors have been tested. 12.5 Separate Business
▶ Data handling is relatively slow, and if there are many Logic . . . . . . . . . 183
12.5.1 What is Business
behaviors, then automation testing is necessary.
Logic? . . . . . . . . 183
▶ Test coverage is always a concern, and hard to prove.
12.5.2 Organization . . . . 185
12.5.3 Separate Data Han-
dling . . . . . . . . . 186
12.1 Extract Utility Routines
Scope Qualifiers
Often you will need to transfer data from a dataset used for the
user interface to a somewhat different dataset which will be passed
to a report or export module. There are often many reasons why
the fields list of one is quite different to that of the other. Sometimes
the reasons are excellent, sometimes merely that two different
developers were involved. This give rise to some very predictable
coding:
type
TXferRec = record
srcField : string ;
dstField : string ;
end ;
const
XferFields : array [] of TXferRec = (
( srcField : ’ RecID ’; dstField : ’ RecID ’) ,
( srcField : ’ UserName ’; dstField : ’ Name ’) ,
( srcField : ’ LastDate ’; dstField : ’ Date ’) ,
( srcField : ’ Items ’; dstField : ’ Qty ’) ,
( srcField : ’ Cost ’; dstField : ’ Price ’)
);
var
idx : Integer ;
srcFld , dstField : TField ;
begin
cdsUI . First ;
while not cdsUI . Eof do
begin
cdsReport . Append ;
for idx := 0 to Length ( XferFields ) - 1 do
begin
srcFld :=
cdsUI . FieldByName (
XferFields [ idx ]. srcField );
dstFld :=
cdsReport . FieldByName (
XferFields [ idx ]. dstField );
if srcFld is TIntegerField then
dstFld . AsInteger := srcFld . AsInteger
else if srcFld is TStringField then
dstFld . AsString := srcFld . AsString
else if srcFld is TDateField then
dstFld . AsDate := srcFld . AsDate ;
end ;
cdsReport . Post ;
end ;
end ;
You will need to create new business logic units which will be the
(testable) home of the large routines which are now on your form.
Similar to the process of moving things to the data module, the
initial move will be little more than a transplant. Along the way,
you will find opportunities for testing and for refactoring. The nice
thing about refactoring is that with each round of activity, the code
becomes less murky, more comprehensible. And to the degree
that you are able to factor out subroutines, especially common
subroutines, you are reducing the maintenance burden of the
future.
The immediate benefit is that you are reducing chaos, and enhanc-
ing your ability to conduct current maintenance with less risk and
more certainty. The growing benefit is that you will keep moving
in the direction of fully testable business logic.
Legacy projects are often so cluttered that you may find it very If a module lacks focus, perhaps it is
difficult to know where to put some pieces of code. Don’t let trying to serve several different areas
of work. If that is the case, then you
that be a barrier to refactoring. Simplification of the existing will do well to separate out multiple
code is essential, and early decisions are subject to change as the modules, and refactor to give each a
appropriate structuring becomes evident. You must recognize that clear focus.
long routines obfuscate structure, and as you chip away at the
clutter, your understanding of the purpose of any piece of code
will increase.
Code development must embrace change. Hitting a moving target is
more challenging, but our work is made far more difficult if we
insist on a fully detailed design before coding.
12.5.2 Organization
Like any other code, business logic must be organized and placed
in suitable modules. Some code will comprise general routines
which can be assembled into business libraries. Other code will
be specific to a particular form. Later in the book I will offer rules
for best practices, but for now, accept that one of them is not to
duplicate code.
If your application is a large one, then consider how best to
organize these modules. One approach would be through file
names, such as these:
▶ f_MainForm—The form module
▶ bl_MainForm—The business logic for the form
▶ d_MainForm—The data module for the form
Given that you are looking at a situation in which all terms must Sometimes you must deal with eval-
be evaluated at once—no hierarchical logic—then partial products uation routines which have side-
effects. In other words, the evalua-
can certainly help. tion itself alters one or more of the
input conditions to the evaluation.
▶ Group together all the positive terms, and separately all
This is a risky practice, and certainly
the negative terms. Alternately, you might wish to create complicates later coding, especially
variables into which to place the complement of the negative as it is not an expected behavior.
terms, so the entire expression can be evaluated in positive
terms.
▶ Create variables for the partial products.
▶ Finally evaluate the simplified partial products.
Most people recognize logical results more easily when the terms In an extreme case I saw a massive
are expressed in positive logic. Occasionally I see code so snarled abuse of the C ternary operator in
which each of the three elements was
up that multiple lines, multiple levels of parentheses, and multiple so long that it required horizontal
inversions are needed to get to the result. It is rare that such a mess scrolling to read the line. And deep
can’t be significantly reduced, and made comprehensible. in that mess was an error in the use
of parentheses. Visual C++ compiled
Sometimes these things evolve because a developer sits down to it; Borland C++ Builder did not. But
add a term, and rather than untangle what was there, just looks what was the effect on the intended
logic?
for the easiest way to add his term and run for cover. Once you
190 13 Fixing Erroneous Coding
if ( CheckBox1 . Checked or
CheckBox2 . Checked or
CheckBox3 . Checked ) then
else
begin
// Active code here .
end ;
var
idx : Integer ;
begin
for idx := 0 to Count - 1 do
begin
// ... code here
end ;
13.4 Loop Forms 191
Or you may recognize that the loop will always execute at least
once, so you might use:
The point is that there are choices, and although the for loop is very
often used, it may not be the most natural choice for the work to
be done. This has more to do with clarity of code than correctness.
The for loop is usually applied when we need to examine each
member of a collection, and when used for a different purpose,
will require us to use Break or Exit for termination. The other loop
forms are inherently conditional, with a test included at either the
top or bottom of the loop. This is a clear signal to anyone reading
the code that the expectation is that the loop will not be expected
to iterate over all members.
192 13 Fixing Erroneous Coding
Personal Preferences
There may be those who prefer the for loop to other varieties,
and as already mentioned, this is not an issue of correctness.
There is value to using the idioms you find easiest to understand,
though in a team situation, others may find your preferences
less than transparent. Keeping things in perspective, there are
many other issues in legacy projects which will be of greater
importance than your choice of loop style.
I do think that it is always beneficial for us to review our own
practices, and sometimes to change them for the sake of clearer
expression of function.
If you use FixInsight, you may also have seen that it will complain
about a for loop in which the index is not referenced. As mentioned
above, it’s not intrinsically incorrect, but less natural and obvious
than using a loop form which is terminated by a condition in data
being met.
In newer versions of Delphi, you also have the option of another
loop form:
for s in AStrings do
begin
// ... code here
end ;
end ;
Using the most natural loop for the task is a matter of clarity, as
are so many things.
At the risk of triggering flames, there are those who object to Some consider these to be as ob-
the use of Break and Continue, almost as strenuously as to the jectionable as goto, but I disagree.
Break and Continue are structured
use of goto. The common objection is to having multiple exits mechanisms which cannot arbitrarily
from a routine, and some will argue that these are merely goto cross block boundaries.
in disguise. Although there is some validity to the view, any
high level language is an abstraction, and a look into the CPU
window will soon show you that jmp label is commonplace,
and jmp is clearly a goto.
I find the arguments against Break and Continue to be rather
weak. They are structured flow management, and I see no
rational objection to their use. That said, it is not unusual that
code can be restructured to make the flow simpler, and that is
always a benefit.
if ConditionA then
begin
// block A
end
else if ConditionB then
begin
// block B
end
else if ConditionC then
begin
// block A
end
else
begin
// block D
end ;
else
begin
// block D
end ;
if ConditionA then
begin
// block A
end
else if ConditionA and ConditionB then
begin
// block E
end
else if ConditionB then
begin
// block B
end
else if ConditionC then
begin
// block C
end
else
begin
// block D
end ;
if ConditionA then
begin
// block A
if ConditionB then
begin
// block E
end
end
else if ConditionB then
begin
// block B
end
else if ConditionC then
begin
// block C
196 13 Fixing Erroneous Coding
end
else
begin
// block D
end ;
The possibilities are huge, and the larger the tree, the more possi-
bilities you must consider. When you analyze the logic flow, do
not be surprised if you find an error or two, particularly where
multiple qualifying conditions are used to select a particular exe-
cution block. That sort of complexity is a major reason to avoid
such constructs wherever possible.
var
SmallIntArray : array [0..15] of Integer ;
// indices need not be zero - based
RangeArray : array [3..28] of Word ;
AnotherArray : array [ Byte , 1..10] of Word ;
// be careful !!
LargeArray : array [ Integer , 0..3] of Word ;
type
TFeatures = ( feDoors , feWindows , feRegisters ,
feReceptacles );
var
Room : array [ TFeatures ] of Integer ;
var
idx : Integer ;
lengths : array of double ; // dynamic
begin
SetLength ( lengths , 12) ;
// now you can assign values :
for idx := 0 to Length ( lengths ) - 1 do
lengths [ idx ] := idx + 1;
end ;
type
TmdFormattable = record
Format : string ;
Value : string ;
end ;
function Fmt (
const Content : array of TmdFormattable ): string ;
var
idx : Integer ;
formats , values : array of string ;
begin
SetLength ( formats , Length ( Content ));
SetLength ( values , Length ( Content ));
for idx := 0 to Length ( Content ) - 1 do
begin
formats [ idx ] := Content [ idx ]. Format ;
values [ idx ] := Content [ idx ]. Value ;
end ;
ApplyFormats ( formats , values );
end ;
The end result is the same, but where the caller creates the Content
array, it will be clear that format/value pairs must be filled.
Support for generics was added in Delphi 2009, and is well worth
looking at if you have not done so. There are many benefits to their
use, including the potential to implement dynamic arrays which
require less checking by the consumer. You can, for example, code
them to allow you to add members without regard to the size of
the array before the assignment. This can be convenient in many
situations, and is helpful in reducing maintenance issues, since
the array itself is designed to avoid range errors.
13.6 Use Arrays 199
The constant array is declared like a static array but the declaration
includes initialization.
const
Months : array [1..12] of string = ( ’ January ’,
’ February ’, ’ March ’, ’ April ’, ’ May ’,
’ June ’ , ’ July ’, ’ August ’, ’ September ’,
’ October ’, ’ November ’, ’ December ’);
case colIdx of
0: Caption := ’ One ’;
1: Caption := ’ Two ’;
2: Caption := ’ Three ’;
end ;
const
colCaps : array [0..2] of string = (
’ One ’, ’ Two ’, ’ Three ’);
// then :
Caption := colCaps [ colIdx ];
This code comes with a risk, however, and might better be coded
with this range check:
const
colCaps : array [0..2] of string = (
’ One ’, ’ Two ’, ’ Three ’);
// then :
Caption := ’’;
if colIdx in [0..2]
200 13 Fixing Erroneous Coding
case colIdx of
0: Caption := ’ One ’;
1: Caption := ’ Two ’;
2: Caption := ’ Three ’;
else
Caption := ’’;
end ;
13.7 Summary
type
TStringHelper = record helper for string
To avoid the problem that only one such helper may be active
at a time, let the unit which contains it be added to the end
of the implementation uses clause, ensuring that if there is
202 14 Class and Record Helpers
another helper for the same in scope, you control which will
win the contest. This may sound risky, but in practice seems
much less so. I have not found any cases where I considered
the benefits to be worth the risk.
Note that when you add methods Often, a compelling reason to use a class helper may be in pre-
with a class helper in this fashion, the senting a more consistent, or at least convenient interface to a
added methods appear to be a part
of the class, but the original methods
component, and one which may allow for added routines which
remain accessible. You will need to de- work around troublesome component functionality. For example,
sign your helpers with this in mind, a spreadsheet interface component I’ve used is inconsistent in
to avoid introducing new problems. its reference to rows and columns. In some calls, it will be (row,
column), but in others, (column, row). By using a class helper I
introduced methods which made the interface consistent.
We have long had the alternative If I seem to be harping on the issue of class helpers and conflicts,
of implementing our own compo- it’s only because this is an important issue. As Delphi adds more
nent which inherits from a commer-
cial component we wish to augment.
class and record helpers, the need to add your own is reduced, at
Though I have done this, I prefer least to augment Delphi libraries. Remember that there is no reason
not to, as there can be complications to implement class or record helpers to augment your own code,
when we update to a newer release as you are able to simply add the functionality to your own classes.
from the vendor. The added function-
ality in the class helper is less directly The most common use case is likely to be in extending or modifying
coupled, and somewhat less likely to the functionality of third-party code, such as components.
be affected by these updates.
203
Class and record helpers in new releases of Delphi create areas you
will do well to leave untouched. The TStringHelper is rather exten-
sive, and very useful. A partial list of record helpers includes:
▶ TBooleanHelper
▶ TByteHelper
▶ TCardinalHelper
▶ TCharHelper
▶ TDoubleHelper
▶ TExtendedHelper
▶ TGuidHelper
▶ TInt64Helper
▶ TIntegerHelper
▶ TNativeIntHelper
▶ TNativeUIntHelper
▶ TShortIntHelper
▶ TSingleHelper
▶ TSmallIntHelper
▶ TStringHelper
▶ TUInt64Helper
▶ TWordHelper
This list is not exhaustive, but suggests the rich possibilities now
supported by class and record helpers. Time spent exploring these
will be rewarded.
My experience has been that implementing a small number of
class helpers in application code brought a great return. Usually, The real beauty of the class helper is
the helpers have been used on complex classes, and have added that its methods appear to be mem-
bers of the class being helped, and
quite a few methods. The benefits were large because the classes it can access the public members of
were widely used, and the helpers made possible a large reduction that class. My methods in helpers are
in repetitious code without creating utilities to which an object usually very small snippets, but very
would have to be passed. useful.
It is true that there are other ways to achieve what the class helper
provides: you could create your own component, for example,
descending from the one you wish to enhance. The creation of a
class helper, however, is less work, and if you really need to add
only a couple of routines, then the class helper will be much easier.
But even for larger collections of routines, the class helper may be a
very attractive solution, as it will usually offer lower maintenance
than designing your own component.
There is also the issue of testing the class helper, but the relative
difficulty of that usually depends on the challenge of testing the
class it helps.
In the end, you must evaluate for your own project(s) the relative
merits of using class helpers, but don’t merely write them off
because some have warned against their use.
14.2 TStringHelper
So the TStringHelper may seem pretty low key, but there is more
here than you may imagine. You will find that using helpers in
fluent coding your code will be dense and concise, without any
loss in readability—in fact, I would argue that the readability is
improved. In part, this comes from reducing the verbosity of the
multiple calls. Looking into the methods, there are some surprises,
as well.
[s , ’ Integer ’]) ;
end ;
Val() is a very old member of the libraries, and tries to deliver the
desired conversion. If it works, then E will be zero. And again, this
doesn’t throw an exception on failure.
Keep in mind also that these helpers support the fluent coding
style, so you could easily write:
var
s: string ;
begin
s := ’my TEST string ; ’
if s. LowerCase . Contains ( ’ test ’) then
DoHandleTest ;
end ;
var
idx : Integer ;
s: string ;
begin
s := ’my TEST string ; ’
// Chars [] is zero - based
for idx := 0 to s. Length - 1 do
if s. Chars [ idx ]. IsUpper then
s. Chars [ idx ] := s. Chars [ idx ]. ToLower ;
14.2 TStringHelper 207
// Instead
for idx := 0 to s. Length - 1 do
if s . Chars [ idx ]. IsUpper then
s [ idx + 1] := s. Chars [ idx ]. ToLower
else
s [ idx + 1] := s. Chars [ idx ]. ToUpper ;
end ;
Notice that the first loop will not compile, because Chars[] is a
read-only property. So the second loop reads from the Chars[idx]
for its test, but writes to the string itself, with an offset index.
By default, strings remain 1-based, but the Chars[] property is
zero-based. Also notice that the TStringHelper has LowerCase as a
member, TCharacter provides ToLower, instead.
Add the availability of the compiler directive $ZEROBASEDSTRINGS
and things get rather complex. Most who have turned on zero-
based strings have soon reverted to the traditional form. The
implementation of zero-based strings seems rather less sweeping
than would have been expected, so surprises lurk. But then you
find things like Chars[] which is always zero-based. The consensus
seems to be that staying with 1-based strings and dealing with the
occasional zero-based feature is more productive than trying to
commit to zero-based strings.
Note also that $ZEROBASEDSTRINGS has been a moving target. At
one point, it was the default in mobile applications, which led
to complications where code was shared between desktop and
mobile environments. In Delphi 10.4 Sydney the default is again to
off.
Test everything!
These are just a few tidbits from a fairly large unit. There is so
much in there that you really should spend some time studying
and appreciating the powers it gives. And all the while, remember
that this near magic is delivered by class helpers with very small
chunks of code.
Judicious use of class and record helpers will allow you to add
safety to your code without adding clutter. The deceptive sim-
plicity of the class helper methods can encapsulate not only safe
conversions but elegant error handling, as well. As with most tools
of good coding, the cost is in good planning. But the reward will
be higher reliability and lower maintenance.
If you have not tried writing a class helper, do yourself a favor.
It’s easy to begin, and once you try them out, you are likely to
find they quickly become indispensable. If you are using a recent
version of Delphi, do keep in mind that there are already class and
record helpers in Delphi. Creating a conflict with them will add to
your challenges, rather than reducing them.
Using a class helper to add functionality to a third-party compo-
nent can be very beneficial. You cannot use them to hide existing
functionality in the component, but sometimes a component may
expose an irregular interface, and carefully adding functions can
increase the regularity, albeit at the expense of increasing the
interface footprint.
You cannot add variables in a class helper, but you may be able
to make use of a variable in the class helper unit, if there is no
risk of conflict. For example, in working with a spreadsheet export
component, adding a CurrentPage variable made it possible to
implement routines in which a page parameter was not needed.
Since the number of operations on a particular page generally
outnumbers the calls to set the CurrentPage, you can gain advantage
in reducing clutter.
Designing class helpers is pretty easy. Most routines will be small.
But you will need to adjust your coding habits a little to deal with
the inability to add instance variables. Also, expect to design and
14.4 More Insights 209
There are many articles online which may help you to make class
and record helpers a part of your tool set. A basic overview is here:
https://www.thoughtco.com/understanding-delphi-class-
and-record-helpers-1058281
Using Datasets Well 15
Data components have been part of Delphi from the start. Capabili- 15.1 TField Properties . . 211
ties vary with the particular component, but most of the properties 15.2 Component Differ-
and operations are much the same from one to another. It is often ences . . . . . . . . . . 212
the case, however, that applications make only rudimentary use 15.3 Normalize Data . . . 212
of these components, leading to the need for more code to be
15.4 Less Specificity . . . . 213
written.
15.5 Think Before Coding 214
Now consider the TDBGrid or other data aware control. The great You the developer must always be
value of such controls is that they are able to present data without aware of content; your code, however,
should be as ignorant of content as
any special coding to manage the presentation. This is based on possible. The TDBGrid just presents,
the controls being aware of the TField type, as well as various of it does not provide business logic
its properties. For example, if the dataset does not populate the apart from what you apply in event
DisplayLabel property, then the column caption will be the field handlers.
name, but if the DisplayLabels have been filled, then the captions
for the columns will be taken from those properties.
When you write code to produce output from datasets, you will do
well to make use of the TField properties, so that you may reduce
the logic you would otherwise need to provide. You may also wish
to consider defining your own scheme for DisplayFormat values,
one which is easily parsed so that you can use a single value and
convert it as needed in report and export modules. This will reduce
the need for conditional logic in connection with presentation.
Production of output in reports and exports is another area in
which the use of constant records may help reduce logic in code.
In one instance, the production of a collection of summaries was
handled with simple loops which had no awareness of data content.
Instead, an array of constant records provided specifics for each
type of summary, and the data content was in a single dataset. The
array of constant records provided the varying column visibility
and other variant parts needed. Design and coding was a greater
effort than otherwise, but once complete, the array of constant
records is the only place changes have been needed to handle
changes in presentation.
214 15 Using Datasets Well
“All programmers are optimists.” – Oddly, the cost of coding is often badly underestimated. You
Frederick P. Brooks Jr (1975). The probably have no unit tests for the existing code, and these must
Mythical Man-Month: Essays on Soft-
ware Engineering. Addison-Wesley.
be created. There is probably no design documentation for the old,
and the code is usually not well commented. Fully understanding
the existing code is a precondition to designing new. Creating unit
tests will have the side benefit of dragging you to a better under-
standing. You may also identify opportunities for improvement,
but these are best set aside until you have good test coverage and
can ensure that you will not damage existing behaviors.
Debugging is always difficult to estimate, but will be facilitated by
well implemented unit tests. You may identify defects in the old
code as you proceed. You may have to preserve some of these to
ensure system compatibility.
Finally, when the new code replicates the behavior of the old, and
passes all the unit tests, you will need to do the actual replacement.
In large applications, it will be best to make the replacement
incrementally; the temptation to replace globally will be great,
but can wreak havoc if some corner case has been missed in unit
testing, and is uncovered in the application behaviors. A new unit
can have a new name, as can a new component type, so they may
coexist in your application. Better safe than sorry.
All of these concerns will affect the real cost of proceeding. Man-
agement will need to be well educated on the trade-offs.
If you do not know the answers, then you are wasting effort.
Maintaining a form which is no longer used is an obvious waste.
But maintaining a form which is used by a small fraction of
your customer base is also wasteful. Worst of all, if you are not
routinely improving the most used forms in your application, your
customers till take you to task, sooner or later.
Guessing at answers to these questions is useless; get data. Then
act on it.
Adding analytics will need management approval, because send-
ing data back to the software publisher makes customers nervous.
That has to be resolved first, and assurances given—perhaps in
218 16 Pragmatism
You may have a collection of mathematical routines in modules Mathematics is only an example; ev-
which were the domain of a single—now departed—developer. ery problem domain has its areas of
specialized knowledge. It is a useful
Although the same strategies would work here, building unit tests, example, as we are not all fluent in
recoding, and replicating behaviors, the costs may be very high. statistical analysis or Calculus.
Once again, the matter of analytics comes into play. You may have
identified dozens or hundreds of modules you wish to rewrite,
but keep in mind that you need management buy-in to be able
to do any of them. Management (and sales) will generally choose
new features even over defect repair, and refactoring will not be
on their list at all.
and which the least. You may occasionally wish to point out that
customer retention is also a measure of return on investment, albeit
one often overlooked. Removing modules no one actually uses
means removing maintenance burden which brings no return.
And that burden is there, always, whether or not it is understood
by product management or sales. If data handling changes, if
columns come and go, then all modules which make use of them
need maintenance, whether or not they are actively used. Keeping
unused features in place is never just noise.
Prioritize your limited resources where they will deliver the
greatest relative value. Analytics will not help much in assess-
ing customer satisfaction, but your support staff will know who
complains about performance, and that will be a great help.
You must find a way to juggle these different priorities, and will
need close coordination with management, support, and sales.
Interfaces 17
Delphi has provided support for interfaces since Delphi 3, when it 17.1 History . . . . . . . . 221
was introduced to link to COM servers. But interfaces in Delphi 17.2 Reducing Scope . . 222
have many more capabilities, and should be among your most used 17.2.1 Constants and Types 222
tools. Some will say you should always code to an interface, but 17.2.2 Properties . . . . . . 223
as with most generalizations, that is oversimplified. Nontheless, I 17.3 Managing Memory 227
will focus here not on COM, but on the value of interfaces inside 17.4 Examples . . . . . . 227
your application.
17.5 Going Deeper . . . 230
17.5.1 Variations . . . . . . 235
17.1 History
17.2.2 Properties
Discipline of Properties
type
TSomeClass = class
private
FHeight : Integer ;
procedure SetHeight ( Value : Integer );
public
property Height : Integer read FHeight write
SetHeight ;
end ;
implementation
type
TSomeClass = class
private
FHeight : Integer ;
procedure SetHeight ( Value : Integer );
public
17.2 Reducing Scope 225
implementation
type
TSomeClass = class ( TSomeAncestor )
private
FHeight : Integer ;
public
property Height : Integer read FHeight ;
end ;
implementation
var
SomeClass : TSomeClass ;
// somewhere in code
SomeClass . Height := 15;
// more code follows
end ;
This looks odd. the Height property is declared as read only, and
now we make an assignment. We can infer that TSomeAncestor
contains a Height property with read/write access. Delphi will
compile this without complaint. But if you run Pascal Analyzer Lite
over your project, it will issue a Strong Warning. This is partly in
the spirit of the original problem, but for a different reason. Delphi
226 17 Interfaces
type
TSomeClass = class ( TSomeAncestor )
private
function GetHeight : Integer ;
procedure SetHeight ( Value : Integer );
public
property Height : Integer read GetHeight write
SetHeight ;
end ;
implementation
var
SomeClass : TSomeClass ;
17.4 Examples
type
IStrings = interface ( IInterface )
[ ’{ FC8E80A5 - A70B -4 BCC -9 BFC -5 BCB15ACF5E4 } ’]
function GetClassName : string ; stdcall ;
function GetCommaText : string ;
function GetCount : Integer ;
function GetStrings (
Index : Integer ): string ; stdcall ;
procedure SetCommaText ( const Value : string );
procedure SetStrings ( Index : Integer ;
const Value : string ); stdcall ;
property ClassName : string read GetClassName ;
property CommaText : string
read GetCommaText
write SetCommaText ;
property Strings [ Index : Integer ]: string
read GetStrings
write SetStrings ; default ;
end ;
FStrings : TStrings ;
function GetClassName : string ; stdcall ;
function GetCommaText : string ;
function GetCount : Integer ;
function GetStrings (
Index : Integer ): string ; stdcall ;
procedure SetCommaText ( const Value : string );
procedure SetStrings ( Index : Integer ;
const Value : string ); stdcall ;
public
constructor Create ( AStrings : TStrings );
property ClassName : string read GetClassName ;
property CommaText : string
read GetCommaText
write SetCommaText ;
property Count : Integer read GetCount ;
property Strings [ Index : Integer ]: string
read GetStrings
write SetStrings ; default ;
end ;
var
SomeStrings : IStrings ;
begin
SomeStrings := TIntfStrings . Create ( AStringList );
var
List1 : TIntfStrings ;
List2 : IStrings ;
begin
// conventional class usage
List1 := TIntfStrings . Create ( AStringList );
try
// operating code here
finally
List1 . Free ;
end ;
// interfaced equivalent usage
List2 := TIntfStrings . Create ( AStringList );
// operating code here
end ;
Note that in this code, both the class and the interfaced class are
public. An alternative approach is to move the class declaration
into the implementation section of the unit. Doing so means that
you must implement a simple function to return an instance of the
interfaced class. Now the front end of the unit looks like this:
unit uInterfaced ;
interface
uses
Classes ;
type
IStrings = interface ( IInterface )
230 17 Interfaces
function GetIStringsInstance (
AStrings : TStrings ): IStrings ;
implementation
var
List : IStrings ;
begin
// interfaced equivalent usage
List := GetIStringsInstance ( AStringList );
// operating code here
end ;
Preventing a user from directly in-
stantiating your interfaced class is The big change is that there is no longer any access to the class
important. You designed the inter-
faced class for a reason, and keeping
type, so only the interfaced version can be used.
the class declaration private makes
The coding of the class itself is trivial, as it adds no capabilities,
clear your intention. This in turn sim-
plifies the design burden, as well as but simply surfaces a subset of the TStrings public interface.
the complexity of testing.
var
sl : TStringList ;
begin
sl := TStringList . Create ;
try
sl . StrictDelimiter := True ;
sl . Delimiter := ’,’;
sl . DelimitedText := StringToDecode ;
// application code
finally
sl . Free ;
end ;
end ;
interface
uses
System . Classes ;
type
IDelimitedText = interface ( IInterface )
[ ’{ FB67C93C -8779 -4 F51 - A257 -37 BC89C4A813 } ’]
procedure Add ( const S: string );
procedure Clear ;
procedure Delete ( const Index : Integer );
function Find ( const S: string ;
var Index : Integer ): Boolean ;
function GetCommaText : string ;
function GetCount : Integer ;
function GetDelimitedText : string ;
232 17 Interfaces
What is missing here, and is not needed, is the string list which
is used in the composition. It can’t be declared here because an
interface can’t define storage. Instead, we define what is needed
for the application of this limited class to our code. As a further
benefit in this design, I use a function to return an instance of the
17.5 Going Deeper 233
class, so that the public interface of the class is only the interface
declaration. That, plus some documentation in the unit which
implements it, will serve to make clear the particular intention of
the module, and warn that the interface should not be altered.
In the implementation section, some differences become appar-
ent:
implementation
uses
System . StrUtils ;
type
TDelimitedText = class (
TInterfacedObject , IDelimitedText )
private
FStringList : TStringList ;
function GetCommaText : string ;
function GetCount : Integer ;
function GetDelimitedText : string ;
function GetDelimiter : Char ;
function GetSorted : Boolean ;
function GetStrictDelimiter : Boolean ;
function GetStrings ( Index : Integer ): string ;
function GetText : string ;
procedure PutStrings ( Index : Integer ;
const Value : string );
procedure SetDelimitedText (
const Value : string );
procedure SetDelimiter ( const Value : Char );
procedure SetSorted ( const Value : Boolean );
procedure SetStrictDelimiter (
const Value : Boolean );
procedure SetText ( const Value : string );
public
constructor Create ;
destructor Destroy ; override ;
// details removed here which are identical
// to the interface
end ;
var
sl : TStringList ;
begin
sl := TStringList . Create ;
try
sl . StrictDelimiter := True ;
sl . Delimiter := ’,’;
sl . DelimitedText := StringToDecode ;
// application code
finally
sl . Free ;
end ;
end ;
Reduces to this:
var
sl : IDelimitedText ;
begin
sl := GetIDelimitedText ;
sl . DelimitedText := StringToDecode ;
// application code
end ;
finally
slThree . Free ;
slTwo . Free ;
slOne . Free ;
end ;
end ;
Is reduced to:
17.5.1 Variations
function GetIDelimitedText (
ADelimiter : Char = ’,’): IDelimitedText ;
begin
Result := TDelimitedText . Create ;
if ADelim <> ’,’ then
Result . Delimiter := ADelimiter ;
end ;
▶ No Separation of Concerns.
Unit testing targets a single unit at a time. If the unit to be tested There will be a need to deal with your
is intimately coupled to several others (and likely, each of these Unit C which uses your Unit B, which
uses your Unit A. This is no different
to still others) then reaching the point of a testable unit will be a than dealing with your units which
long struggle. use Delphi library modules. The prob-
lem is solved by first unit testing the
If one or more of these units involves a live database, then obtaining lowest level unit, then the unit which
a reliable test result is problematic: what if the database has been uses that, and so on.
compromised by another developer?
To make sure your units are testable, there should be a clear list of
requirements. These will not be detailed, as they must be general.
But the list is realistic, and you may have difficulty achieving it.
▶ The unit should not depend on other units in your applica-
tion, or if it does, those units must be fully testable.
▶ The routines in the unit should present a simple interface
and a clear result. This allows the tests to be written logically
and with full coverage of the logical possibilities.
▶ If a routine to be tested relies on another routine in the
module, that routine must be fully tested in the sequence
first.
▶ The tests implemented must exercise the full range of possi-
bilities available in the routine under test.
▶ The coding is not complete until all tests are passed or failed
(both outcomes are designed into testing.)
▶ When the module under test is modified, the tests must be
updated to exercise the new possibilities.
This may initially sound like gobbledygook but as you begin
to implement unit tests, these need for these requirements will
become clear.
If you are concerned only with Delphi 2010 and later, you may
prefer to use DUnitX, to take advantage of its particular features.
Some of us, however, still maintain projects in older versions, and
will therefore need to use DUnit, the original. Which framework
you use is less important than that you implement unit testing,
as soon as you are able to do so. For anyone new to the topic, I
1: The Art of Unit Testing: with Exam- recommend The Art of Unit Testing: with Examples in C# 1 , as most
ples in C#, by Roy Osherove, 2013 Delphi developers will find it somewhat easier to make sense of
C# than Java.
Test coverage will verify not only suc- When you write unit tests, remember that your goal is to prove that
cess, but must also verify graceful your code does not fail to perform properly, regardless of input. You
failure, by which we mean that your
tests should verify not only success,
should be coding defensively, as someone will call your routines
but that in failure, you do not allow with bad data, sooner or later, and therefore you need to code
unhandled errors to escape. your tests to verify that such cases are covered. Exception handlers
(see section 4.5) should not be seen as regular program logic, but
as solutions to the handling of exceptional and unexpected error
conditions. Error conditions which we can reasonably anticipate,
such as a lack of network connection, or unfound URL, should not
be resolved through exception handlers.
Interactive testing often begins with the happy path, meaning that
you call with exactly the sort of parameters you expect to process.
Unit testing needs to consider not only the happy path, but the
ugly back roads, and should exercise those possibilities. Your goal
is to provide exhaustive testing, so that the routines can be used
with no risk of unhandled failure. If you coded the routines, you
are in a good position to implement full coverage in your tests. If
you inherited the routines from someone long gone, then you are
likely to find that some tests may need to be added later, if corner
cases were overlooked.
To simplify, you will write tests which need to pass, and tests
which need to fail. In some cases, you may also need tests to show
that failures happened in the expected manner, not merely that
they failed. Remember that the ultimate goal is perfect reliability
18.10 Testing Practices 243
19.1.1 Profiling
Order of Actions
In a perfect world, we might suggest that you defer performance
tuning until you have fully restructured your code. But legacy
applications are generally shipping applications and users tend to
be impatient with performance issues. As with most activities
in reworking legacy projects, we must be pragmatic.
Even in legacy projects, some modules may be relatively de-
coupled, and we should seize the opportunity to advance our
cause in such cases. When you find a module which has min-
imal involvement in UDCs, try to eliminate those altogether.
Sometimes it will require only the relocation of a routine or two
to a different module. Then create unit tests for the module, and
try to make them as comprehensive as possible. Understand
that you will almost certainly need to revisit the unit tests as
you identify other cases which need testing.
Once you have reached that point, and if the module is widely
used, then you may wish to do some profiling work. Since the
module is now free of unit dependencies, and unit testable,
create a special project for the profiling work. As you alter the
code for performance, remember to repeat the unit tests, to be
sure you have not introduced defects.
19.1 Realities 247
At first, you may think you will profile all of your code. It’s a
nice idea, but probably unrealistic, as you may have thousands
of modules, and each of them represents considerable work to
profile, modify, and retest. Instead, you should be guided by the
observed performance of your application, or by operations which
can be expected to take a long time.
Perhaps you have built a tool to analyze your project files for some
particular characteristic. You will iterate through your project
files, parsing and collecting the requisite data. Perhaps you will
also apply some corrections. If the tool is expected to process
large projects repeatedly, then this is an operation well worth
profiling.
Again, as you profile, you will need to make changes to code, so
you should be prepared for thorough unit testing.
There are things that you will not optimize with simple code
rework. And there are code optimizations which will be altogether
masked by the overhead of other resources, such as a database.
Program performance is sometimes very hard to improve. In a past
case, days of examination in code got me almost no improvement,
but replacing a memory dataset with a different variety of dataset
made a huge difference with no code changes.
Optimization requires that you identify the actual bottlenecks,
and ignore the supposed bottlenecks.
You will not achieve dramatic results through guesswork. The tools
you select may be fancy and expensive, or basic and inexpensive,
but as long as they let you see where the major time consumers
are, you can accomplish what is needed. There are a number of
profilers for Delphi:
▶ AQTime (https://smartbear.com/product/aqtime-pro/
overview/) It’s pricey, but gets very positive reviews from
people who use it regularly.
▶ AsmProfiler (https://github.com/andremussche/asmpro
filer) Free and open source. On that basis alone, you may
want to try it.
▶ ProDelphi (https://www.prodelphi.de) Inexpensive and
capable, though with fewer features than some. I’ve used it
for years, and have been happy with it.
▶ Sampling Profiler (https://www.delphitools.info/sampl
ingprofiler/) Free, but closed source. Last updated in 2013,
but there are people who speak very favorably of it.
This list is sorted, not ranked.
It is not uncommon to discover through profiling that some routine
is called many times more than you thought, or than necessary,
because of some error in code flow. When code performance in a
routine is the issue, you may well discover the issue is not better
[6]: Gabrijelčič (2018), Delphi High Per- coding, but a better algorithm. See [6] for excellent coverage of
formance profiling in practice.
Disruptive Forces 20
With all this talk of refactoring, the task of updating legacy code 20.1 Code Details . . . . 249
sounds almost easy. But of course, it always appears simple in 20.1.1 Hints and Warnings 249
a book. Often, it is anything but simple. There are a number of 20.1.2 Static Analysis . . . 250
challenges which may multiply the difficulties you face. 20.2 Component Issues 251
20.2.1 Orphaned Compo-
nents . . . . . . . . . 252
20.2.2 Local Components . 252
20.1 Code Details
20.3 Dependency Cycles 255
20.3.1 The Gordian Knot . 256
There are cleanups which you can begin at any point, and which 20.3.2 Small Steps . . . . . 256
will reduce the level of noise as you proceed. These really should 20.3.3 Larger Steps . . . . . 257
be attacked before taking on any of the issues in the subsequent 20.3.4 Cycles in Compo-
sections. nents . . . . . . . . . 258
20.4 Compiler Versions 259
20.5 Issues You must
20.1.1 Hints and Warnings Find . . . . . . . . . 260
Apart from compiler errors, hints and warnings are among the
greatest value you will obtain from a build cycle. The correct num- Consider each hint or warning to
ber of each is zero. It is common in legacy projects for the numbers be a defect that simply hasn’t been
triggered yet.
to be in three or four digits. Each of these is a potential problem in
the field. Do yourself a favor, and make the elimination of these
problems—they are not potential problems—a high priority.
If there are messages about platform-specific routines, for example,
then make use of the conditionals provided by Delphi to suppress A routine specific to Windows, for
them. As they are expected, you don’t need to be reminded. On example, is not cause for concern if
you are designing only for Windows.
the other hand, messages which are not expected indicate clearly
the need for repairs.
You may see hints and warnings you think are unimportant. Think
again. When the compiler warns of an implicit string cast, for
example, that may be happening because you moved code from
an earlier version with no support for Unicode to a more recent
one. Sometimes it is a matter of a local variable that is (perhaps
needlessly) declared as an AnsiString, or a shortstring. Most of the
time, such things are easily resolved, and the sooner you clear up
your hints and warnings, the sooner you can focus entirely on the
major issues. If you brought hints and warnings with you from an
250 20 Disruptive Forces
Component Focus
Component Pollution
Less is More
Early in the life of Delphi projects there seems to be an attraction
to using as many different components as possible. There is
some excitement to learning what has been done better than in
another similar component. But if the experiment shows it to
truly be better, should it not then replace all similar instances?
Use as few different component types as possible. To repeat
some advice:
Everything should be made as simple as possible,
but not simpler. – Albert Einstein
The Gordian knot legend is useful. Once UDCs enter your application, they multiply. They are possibly
(https://en.wikipedia.org/wiki/ the ultimate manifestation of tight coupling, and will paint you
Gordian_Knot)
into a corner with breathtaking speed. We know (or should) that
tight coupling is a major enemy of clean code. Chains of unit
dependencies multiply your problems because the modules in
question are widely used, so the risk inherent in changing how
they work may impact hundreds of other modules.
You cannot unit test tightly coupled modules because they cannot
be isolated from their coupled brethren. That is a very serious
impediment which stands between you and reliable code. Since
There is a Catch-22 aspect to this, as unit testing is critical to the refactoring process, eliminating those
mentioned earlier in the book. You UDCs must be a high priority.
need unit testing, but can’t achieve it
without modifying code. This is why If you approach your Gordian knot as did Alexander, you will
the elimination of UDCs must be a
create a ruin. We have already explained why redesign and rewrite
high priority.
is not a practical alternative, and that is where a sword or machete
will lead you.
Refactor Utilities
Move data manipulation off your forms. Start small, with a form
which only contains one or two datasets. Create a datamodule for
them, and assign it a name which matches the form name but
with a different prefix. Follow whatever conventions are normal to
your project, or design new conventions if the old were irregular.
258 20 Disruptive Forces
Now replace all the direct code in the form with references to new
routines in the datamodule.
Since each of these actions affects only code which made direct
access to datasets, it is readily identified and easily isolated.
Keeping it Simple
Keeping it Simple
Facilitate Maintenance
Code for easy readability and simple debugging. Very compact
code can hinder both. Good code is tight and expressive, not
terse and incomprehensible.
There is no automated tool which can replace thoughtful consid- Be open always to writing your own
eration and coding, or human recognition of patterns. There are tools. Keep them simple, and do not
try to build a super tool—the effort
commercial software tools which can help, but none of them I is smaller, and the payback larger.
have used provides all the elements I have found useful or even
necessary.
Patterns pasted into code can later be made to appear different
when someone inserts code into the middle of the pasted lines.
That’s less difficult for a human to recognize than for a utility
program. You certainly can try writing your own tool to recognize
the modified patterns, but will it be worth the effort? After all,
unless you use a formal parser like DelphiAST, you will be having
to deal with all the complexity of the source on your own. And
even with DelphiAST, there is much to handle. Then if you wish
it to apply corrections, you multiply the complexity. Most of the
tools available to assist with any sort of analysis do only that, the
262 20 Disruptive Forces
analysis. Then you are left with a large list of issues to resolve by
hand.
Each of the available analysis tools is limited in what it does, and
in how well it does the work. All of them which I have used leave
the rework to the developers. In the end, it is probably for the best.
An automated tool could wreak havoc in very little time.
Best Practices
Some Simple Principles 21
Principle of Least
21.1 Principle of Least Astonishment 21.1
Astonishment . . . . 265
21.2 DRY: Don’t Repeat
From (https://en.wikipedia.org/wiki/Principle_of_least Yourself . . . . . . . 266
_astonishment) Wikipedia: 21.2.1 The Rule of Three . 266
21.2.2 Causes of Repetition 267
The Principle of Least Astonishment, also called the
21.2.3 Using the Libraries . 267
principle of least surprise (alternatively a “law” or
21.2.4 String Utilities . . . 268
“rule”) applies to user interface and software design. 21.2.5 Date and Time Utili-
A typical formulation of the principle, from 1984, is: ties . . . . . . . . . . 268
“If a necessary feature has a high astonishment factor,
21.3 YAGNI: You Ain’t
it may be necessary to redesign the feature.” Gonna Need It . . . 269
A more general statement of the principle is that a 21.4 SOC: Separation of
component of a system should behave in a way that Concerns . . . . . . . 270
most users will expect it to behave; the behavior should
not astonish or surprise users.
In other words, the operation of a system element should be intu-
itive. At the development level, this means that to someone with Intuitive is a horribly overworked
reasonable experience in coding and with the problem domain, it word for a quality which is rarely
achieved.
works in obvious and sensible fashion.
Although this is very commonly applied in connection with the
perception of the application by users, it is worth application to
coding practice, as well. People’s expectations affect their comprehen-
sion. This is one reason that certain patterns become idiom: they
are so familiar as to be transparent to our thought processes.
We have all had the experience of reading some code and asking
“what were they thinking?” That sense of surprise is exactly the Many of us will even acknowledge
point here. I will include in this section a warning against clever having had that exact experience
while reading our own code, years
code, the sort of thing which may seem to illustrate a deep under- later. Such recognition is painful, but
standing of compiler internals, but provokes curses in maintenance a valuable lesson.
operations. Code should always be clear, even obvious in what it
is meant to achieve.
Any fool can write code that a computer can under- Computers have no difficulty un-
stand. Good programmers write code that humans derstanding, for example, GOSUB
11503.
can understand. – Martin Fowler
266 21 Some Simple Principles
dl := TStringList . Create ;
try
dl . Delimiter := ’,’;
dl . StrictDelimited := True ;
dl . DelimitedText := MyText ;
// real work here
finally
dl . Free ;
end ;
dl := IDelimitedText . Create ;
dl . DelimitedText := MyText ;
// real work here
When did you last read a library module? Or even skim it? There
is gold to be mined in those modules. Make time to explore, if not
in source, at least in the help modules.
268 21 Some Simple Principles
Simplify your code and your life: Do not implement what is not
needed. If requirements change tomorrow, then code to the new
requirements when they appear, not before. The real cost is not
simply in writing the routines, but in fully implementing the unit
testing needed to ensure they are reliable.
Wikipedia (https://en.wikipedia.org/wiki/Single_respons
ibility_principle) again:
The single responsibility principle is a computer pro-
gramming principle that states that every module,
class, or function should have responsibility over a sin-
gle part of the functionality provided by the software,
and that responsibility should be entirely encapsu-
lated by the class. All its services should be narrowly
aligned with that responsibility. Robert C. Martin ex-
presses the principle as, “A class should have only
one reason to change,” although, because of confusion
around the word “reason” he more recently stated
“This principle is about people.”
As is so often the case, this principle is sufficiently abstract as to
allow for a great deal of interpretation. However, in the current
discussion, I think it makes sense to compare to what is often seen
in legacy code:
object which contains a variety of data, thus freeing the class from
specific knowledge of the particular combobox and its form.
Under the Single Responsibility Principle, however, the class would
not interact with other controls on the form, nor would it have
responsibility for interaction with a database.
22.1.2 Open/Closed
This principle has to do with the developer’s view of, and respon-
sibility to, a class. As Wikipedia states, credit to Bertrand Meyer
on this.[13]. [13]: Meyer (1988), Object-Oriented
Software Construction
▶ A module will be said to be open if it is still available for
extension. For example, it should be possible to add fields to
the data structures it contains, or new elements to the set of
functions it performs.
▶ A module will be said to be closed if [it] is available for use
by other modules. This assumes that the module has been
given a well-defined, stable description (the interface in the
sense of information hiding).
From (https://en.wikipedia.org/wiki/Liskov_substitutio
n_principle) Wikipedia:
Substitutability is a principle in object-oriented pro-
gramming stating that, in a computer program, if S is
a subtype of T, then objects of type T may be replaced
with objects of type S (i.e. an object of type T may be
substituted with any object of a subtype S) without
altering any of the desirable properties of the program
(correctness, task performed, etc.). More formally, the
Liskov substitution principle (LSP) is a particular defini-
tion of a subtyping relation, called (strong) behavioral
subtyping, that was initially introduced by Barbara
Liskov in a 1987 conference keynote address titled Data
abstraction and hierarchy. It is a semantic rather than
merely syntactic relation, because it intends to guar-
antee semantic interoperability of types in a hierarchy,
object types in particular. Barbara Liskov and Jeannette
22.1 What is SOLID? 277
var
Fld : TField ;
begin
Fld := cdsSomeTable . FieldByName ( ’ Cost ’);
if Fld is TNumericField then
Fld . DisplayFormat := ’ 0.00 ’;
From (https://en.wikipedia.org/wiki/Interface_segregat
ion_principle) Wikipedia:
In the field of software engineering, the interface seg-
regation principle (ISP) states that no client should be
forced to depend on methods it does not use. ISP splits As seems obvious, this principle ties
interfaces that are very large into smaller and more back to the notion of YAGNI, as well.
An interface which is larger than
specific ones so that clients will only have to know needed adds possible confusion, and
about the methods that are of interest to them. Such the certain need for maintenance.
shrunken interfaces are also called role interfaces. ISP
is intended to keep a system decoupled and thus easier
to refactor, change, and redeploy. ISP is one of the five
SOLID principles of object-oriented design...
This gives rise also to the question of how to subdivide the larger
interfaces. There are a number of options, depending on what fits
your requirements:
278 22 SOLID
Notice that:
▶ FillList depends on the TStrings object
▶ FillList does not create the object, does not own it, does not
free it.
That is a trivial example of dependency injection. FillList was
written for a purpose, but does not participate in the lifetime man-
agement of the list object. Lifetime management is a responsibility
Always handle object lifetime by de- of the owner of the object. Obviously, in this case, that means
sign. It is simpler and much easier the caller of FillList or something at an even higher level. If an
than trying to fix it in the debug pro-
cess.
object is created in the constructor of its owning class, it should
be disposed of in the destructor of the owning class. Think of this
as a matter of symmetry: creation and destruction are handled at
the same level. Any other approach—other than with some sort of
automated management (interfaces or garbage collection)—will
lead to defects.
Another simple example could be when you pass a TRect to another
routine which then acts on that TRect. In the case of the TStrings
object, the routine altered its contents; with the TRect, it may
simply have read the contained values and left them unaltered.
In both cases, the objects in question were needed for the called
routines to do their work.
If there were no more to Dependency Injection than that, there
would be nothing to explain. Let’s consider why the owner should
create and destroy its owned objects. FillList is called from some
number of modules. One of those might have been responsible
to instantiate the class. Perhaps it was a form which instantiated
the class, and passed self as the owner. That guarantees that the
class will be freed eventually, and its destructor will dispose of its
owned objects. We don’t know or care when these actions occur,
only that they will. the Delphi runtime is responsible for these
actions; our code is not.
It is always best to take advantage of automated cleanup when
you can. Letting a form free its owned objects is one of those cases
where you can do so. And that reduces the responsibilities on
your code.
When an object is instantiated with nil as owner, then you must
ensure it is freed. If you instantiate it from module A, and free
it from module B, you have created a time bomb. Sometime in
the future, someone else will inevitably alter the code flow while
22.1 What is SOLID? 281
resolving some other issue, and the result is a memory leak. That’s
another aspect of tight coupling. In this case, the coupling is with
respect to the lifetime management. Reconsider your design, and
make module A responsible to instantiate and free, and let module
B simply make use of the object.
A Concrete Definition
Citing again from Nick Hodges, there are three main categories of
dependency injection:
▶ Constructor injection. In this form, dependencies are passed
to the class constructor.
▶ Method injection. Here the dependencies are passed as pa-
rameters to a method.
▶ Property injection. In this type, a dependency is assigned to
a property of the consuming class. This is particularly useful
if the consuming class may not always need the dependency
item.
In most of these references, you will find the assumption that
dependency injection is used by classes, and often this is the
case. But as with the FillList example I showed at the outset,
procedural code can also employ dependent objects.
In all of these alternatives, the benefits gained are comparable. You
can make extensive use of these approaches in updating legacy
282 22 SOLID
[11]: Hodges (2017), Dependency Injec- I will give scant coverage to the Dependency Container here,
tion in Delphi and recommend highly that you read Nick’s[11] book for deep
coverage. He also provides sample code which will facilitate your
exploration.
In simple terms, the Dependency Container(DC) is a dictionary
which contains links to the registered dependency objects. The
DC is also responsible to manage the lifetime of the objects. Given
these responsibilities, it frees you from creating and destroying the
registered object types. On the other hand, if you do some modest
amount of searching online, you will also find criticisms of the DC
as a code smell or an anti-pattern, a topic Nick also discusses.
In your application initialization, you must register each of the
types which will be supported by the DC. Once that housekeep-
ing is accomplished, you may move on, largely oblivious to the
management of those objects.
23.1 Inheritance
23.2 Composition
type
TSomeClass = class
private
FStrings : TStrings ;
public
constructor Create ( AList : string );
destructor Destroy ; override ;
end ;
implementation
TSomeClass . Destroy ;
begin
FStrings . Free ;
inherited ;
end ;
23.3 Aggregation
type
TSomeClass = class
private
FStrings : TStrings ;
public
property Strings : TStrings
read FStrings
write FStrings ;
end ;
24.2.1 Create/Destroy
var
SomeObj : TObject ;
begin
SomeObj := TObject . Create ;
try
// some code here
finally
SomeObj . Free ;
end ;
end ;
var
SomeObj : TObject ;
begin
SomeObj := TObject . Create ;
// some code here
SomeObj . Free ;
end ;
24.2 Delphi Idioms 291
Terrible! Was SomeObj successfully created? If not, then trying to We tend to assume that a construc-
tor succeeds. But what if it fails be-
free it will throw an exception. Did the code executed before the
cause of some external system? Or
call to free throw an exception? Then the object is not destroyed. because of bad coding? Our coding
must account for such possibilities,
Keeping construction and destruction in the same scope is impor- or the user will be greeted by excep-
tant. If SomeObj is to be created by ModuleA, then it should also be tion messages which should not be
freed in ModuleA. To do otherwise is very risky, and tedious to seen in the field.
debug. If that seems too heavy a constraint, you should consider
using interfaced objects, so you need not manually manage their
destruction.
And yes, you could FreeAndNil(SomeObj) if you prefer. But it is
difficult for me to like FreeAndNil, which seems to resemble an
ill-used try/except clause which merely eats its exceptions. As
FreeAndNil can be applied to an object already freed, then its
failure to thrown an exception may mask real issues which need
correction.
When multiple objects are to be managed, then the canonical form
of the try/finally idiom begins to look awkward:
var
FirstObj , SecondObj : TObject ;
begin
FirstObj := TObject . Create ;
try
SecondObj := TObject . Create ;
try
// some code
finally
SecondObj . Free ;
end ;
// some code here
finally
FirstObj . Free ;
end ;
end ;
var
FirstObj , SecondObj : TObject ;
begin
try
FirstObj := TObject . Create ;
292 24 Design Patterns
var
FirstObj , SecondObj : TObject ;
begin
FirstObj := nil ;
SecondObj := nil ;
try
FirstObj := TObject . Create ;
SecondObj := TObject . Create ;
// some code here
finally
SecondObj . Free ;
FirstObj . Free ;
end ;
end ;
This is safe because you initialized the pointers to nil before at-
tempting to create instances. There is one issue remaining, which
is that if the call to SecondObj.Free throws an exception, then
FirstObj.Free is not called. But is a cardinal rule in Delphi that a
destructor may not throw an exception, so in developing your own
classes, make sure you prevent that possibility. How you accom-
plish that will vary, but this is a rigid requirement in Delphi for
good memory management. For more detail, see Dalija Prasnikar’s
[15]: Prasnikar et al. (2018), Delphi excellent book[15] on memory management.
Memory Management
24.2.2 If/Else
var
s : string ;
begin
if Cond then
s := ’ True ’
else
s := ’ False ’;
var
s : string ;
begin
s := ’ False ’;
if Cond then
s := ’ True ’;
{ alternately }
s := IfThen ( Cond , ’ True ’, ’ False ’);
The result is the same, but in some versions of Delphi, you may
see a hint or warning that it may not have been initialized in
the first version. The cost of a string assignment that may not be
used is minuscule. If you are dealing with a situation in which an
assignment has a significant performance cost, then you should
prefer to assign initially from the one with the lower cost.
294 24 Design Patterns
24.2.3 Helpers
Any class can have a class helper, but only one will be in scope
at any time. The documentation suggests there can be only one,
but that is not strictly true. You could have different helpers for
the same class, but only one will be active in any scope. For
example, you could write a class helper for strings, and put it in
the implementation uses clause of one of your modules. It will then
replace the Delphi string class helper, as it is the most recently seen
by the compiler. This is complicated and risky, and not suggested,
but possible.
You can introduce class fields in the helper, but no instance fields.
You are writing a helper, a supplement to the class, not a full class.
The logic here is that class fields become global to the class. But in
the class helper, there is otherwise no storage.
Operator overloading is not supported. Once again, this is a
limitation imposed on the class helper for a reason. Were it possible
to add operator overloading, that would have to alter the behavior
of the class to which it is attached. A class helper supplements
operations of the class, but cannot alter them.
A great benefit of class helpers is that they may be used to add
functionality to third-party classes and components, which may
be beneficial for many reasons.
unit u_SmartPtr ;
interface
uses
SysUtils , System . Generics . Collections ;
type
TSmartPointer <T: class > = record
private
FValue : T;
FFreeTheValue : IInterface ;
function GetValue : T;
public
constructor Create ( AValue : T) ; overload ;
property Value : T read GetValue ;
end ;
implementation
begin
FValue := AValue ;
FFreeTheValue := TFreeTheValue . Create ( FValue );
end ;
{ TFreeTheValue }
end .
var
SomeList : TStringList ;
SomeList_ptr : TSmartPointer < TStringList >;
begin
SomeList := TStringList . Create ;
SomeList_ptr . Create ( SomeList );
// usage code here
If you use the Shared record from Spring4D, then it would look
like this:
var
SomeList : IShared < TStringList >;
begin
SomeList := Shared . New ( TStringList . Create );
// usage code here
298 24 Design Patterns
The list is created, then the smart pointer is created, with custody
of the list. When the list goes out of scope, the smart pointer will
free it.
If you wish to use the more comprehensive capabilities of Shared,
from (https://bitbucket.org/sglienke/spring4d/wiki/Home)
Spring4D , you will not go wrong.
The example in the interfaces section in which an interfaced object CSV stands for comma separated
operated on a contained TStrings is a reasonable starting point. If values, such as you will see in
the CommaText property of a
you have occasion to use CSV strings, then you almost certainly TStringList.
make use of a TStringList with StrictDelimited set to true. There
is some boilerplate code you must remember to apply, and of Setting StrictDelimited to True
course, you must manage the life of the object. and then using the DelimitedText
property of a TStringList over-
Alternately, you could create an interface to expose the operations comes some unfortunate defects
in the implementation of the
and properties you need, and then build the class to fulfill the
CommaText property. In general, it
interface. In its constructor, you would put the property settings for is best to use DelimitedText, as
the StrictDelimited operations. You might decide not to interface the results are reliable and consis-
the Text and CommaText properties, as this will be used only for CSV tent, whereas CommaText can sur-
prise you.
strings, and limiting the interface will ensure proper application.
Once that has been written—and tested, of course—you are on
your way to simplifying the handling of your CSV strings. And
you should be able to remove clutter from one or more units in
your project, as the replacement proceeds.
Deeper coverage may be found in Primož Gabrijelčič (see [7], page [7]: Gabrijelčič (2019), Hands-On De-
150). As with any pattern, there are details which are essential: sign Patterns with Delphi
24.5 Summary
interface
TUseList = class
private
FList : TStringList ;
function GetCommaText : string ;
procedure SetCommaText ( const Value : string );
public
constructor Create ;
destructor Destroy ; override ;
property CommaText : string
read GetCommaText
write SetCommaText ;
end ;
25.1 Starting Small 305
implementation
TUseList . Create ;
begin
FList := TStringList . Create ;
end ;
TUseList . Destroy ;
begin
FList . Free ;
end ;
interface
type
TUseList = class
function GetCommaText : string ;
procedure SetCommaText ( const Value : string );
public
constructor Create ;
destructor Destroy ; override ;
function FindItem (
const AItem : string ): Integer ;
property CommaText : string
read GetCommaText
write SetCommaText ;
end ;
implementation
TUseList . Create ;
begin
FList := TStringList . Create ;
end ;
TUseList . Destroy ;
begin
FList . Free ;
end ;
306 25 Dependency Injection
Clearly, this will work, but it is not very remarkable. The lifetime
of the list is the responsibility of TUseList. On the other hand, the
content of TUseList is controlled by the user of the class, through
the CommaText property.
To emphasize just a bit, if TUseList exists, then CommaText exists,
and may safely be accessed from outside the class. The caller may
yet have neglected to populate the list, but that will result in a
failure of function, not in an access violation.
There is a very compact presentation on Dependency Injection on
this site: (https://marcogeuze.com/2020/11/11/dependency-
injection-in-delphi/.) The author shows a very simple example
with strong coupling, and then demonstrates how to first isolate
a dependency through use of Dependency Injection and then how
to remove the dependency on concrete implementation by using
interfaces. Clean, simple, and concise, which is why I linked it.
None of these is to be viewed as the best type, and each has its
areas of application. Be skeptical of dogmatic arguments which
would exclude any of these, or which contend that DI without a
container is in some way irrelevant. Success in reworking legacy
25.2 Types of Injection 307
Complex Dependencies
Such a record holds data which are all clearly related to a single
book. Using such a record is more convenient than passing
each member in its own parameter.
uses
System . Generics . Defaults , System . Generics .
Collections , System . AnsiStrings ;
type
{ Declare a new custom comparer .}
TIntStringComparer = class ( TComparer < String >)
public
25.2 Types of Injection 309
function Compare (
const Left , Right : String ): Integer ; override ;
end ;
{ TIntStringComparer }
function TIntStringComparer . Compare (
const Left , Right : String ): Integer ;
var
LeftTerm , RightTerm : Integer ;
begin
{ Transform the strings into integers and perform
the comparison .}
try
LeftTerm := StrToInt ( Left );
RightTerm := StrToInt ( Right );
Result := LeftTerm - RightTerm ;
except
on E : Exception do
begin
writeln ( ’ Not a number ! ’);
Result := CompareStr ( Left , Right );
end ;
end ;
end ;
procedure SortMemos (
const Comparer : IComparer < String >) ;
var
List : TList < String >;
I : Integer ;
begin
Randomize ;
{ Create a new list of strings with the custom
comparer . }
310 25 Dependency Injection
FillList (5) ;
{ Free resources .}
List . Free ;
end ;
var
Cmp : IStringComparer < string >;
begin
{ Use our custom comparer . }
Cmp := TIntStringComparer . Create ;
SortMemos ( Cmp );
readln ;
end .
Read with care the last few lines of code. Cmp is declared as being
type IStringComparer, and then populated. Cmp is then passed to
SortMemos, which requires a parameter of type IComparer<string>.
Since TIntStringComparer fulfills IComparer<string>, it is suitable.
Method Injection works well where the dependency fulfills a need
specific to the call, but is not related to the life of the object
instance.
25.3 Summary
The practice of Test Driven Design (Test Driven Design) has perhaps
added some confusion. Some proponents of Test Driven Design in
their zeal may lead you to believe that if you have existing code, it
is too late. Too late to let Test Driven Design control the production
of existing code, certainly, but never too late to put unit testing
into practice.
Consider these steps:
▶ Select a unit you wish to unit test. It should be relatively
small, and without many dependencies on other modules.
▶ If your chosen module is tangled in unit dependencies, try
to minimize those through refactoring.
▶ Write tests for each of the routines in your test unit. Create
tests which should pass, but also tests which should fail. A
graceful failure is an important part of stable coding.
Plan for organization of your unit testing modules. Not the mod-
ules to be tested, but those which contain the tests. They should be
as well organized as your project code. And you will want them in
your source code repository, as well—they are assets, and will live
as long as the modules to which they apply. Moreover, they will
be subject to test and revision, as much as any other modules.
Writing your testing modules will be affected by the test framework
you select. If the framework you select supports using attributes
to define test values, that may lead you to increased coverage in
your testing, compared to the coding of individual test routines.
There are as many ways to approach the coding of tests as there
are to the coding of applications—take some time to experiment
26.1 DUnit 315
Disclaimer
My experience has been almost exclusively with DUnit, so I
will not be offering any detailed coverage of the other unit
test tools. That said, there seems to be consensus that DUnit 2
and DUnitX bring enhancements, but that the original DUnit
remains perfectly serviceable.
26.1 DUnit
The oldest of the unit test tools for Delphi, DUnit was created on
the model of JUnit, and is found here: (http://dunit.source
forge.net/.) I’ve not found the date of its first release, but in
mid-2000 it was moved to SourceForge, and the introductory article
(https://web.archive.org/web/20111019190343/http://ww
w.suigeneris.org/writings/1999-11-29.html) by Juancarlo
Añez, who did the initial work, was published on 29 November
1999. This continues to be a viable framework for unit testing, and
if you need support for earlier releases of Delphi, then it may be
the one you must use.
A further value is that DUnit is a part of Delphi, and enjoys some
support within the newest Delphi releases. (https://docwiki.em
barcadero.com/RADStudio/Sydney/en/DUnit_Overview)
DUnit is straightforward to use, and getting started is easy. As
you proceed, you will develop your own strategies, but keep in
mind that tests should be simple, clear, and fast. You will run them
again any time you alter the code they test. More coverage will be
found in Chapter 33.
26.2 DUnit2
26.3 DUnitX
26.4 TestInsight
If you are using Delphi XE or later, then you may wish to add
TestInsight to your IDE. It is a plug-in which provides support for
using DUnit, DUnit 2, and DUnitX. Written by Stefan Glienke, it
can be found on bitbucket (https://bitbucket.org/sglienke/t
estinsight/wiki/Home), and as explained on that page:
TestInsight integrates unit testing into the Delphi IDE
▶ No need to run your tests externally anymore - you can see
the results inside the IDE
▶ Run tests from the code editor and navigate to the failing
code from the results overview
▶ Continuously runs your tests bringing test driven develop-
ment in Delphi to the next level!
Stefan is very active on Delphi-Praxis, and the issues list shows
clearly that the code is in current maintenance. More coverage will
be found in Chapter 43.
27.2 Standalone
In the image above, the orange colored boxes are on the form itself,
and the yellow are on the panel. Different colors are assigned to
controls in each container on the form, for clarity.
There are many details handled in this wizard, including:
328 28 CnPack
When you invoke the Uses Cleaner on the current unit, if it is not
in the project, the Current Unit radio button will be disabled. When
that happens, close the wizard, add the unit to the project, and
again select the Uses Cleaner.
It is good to do a compile before running the Uses Cleaner, because
if you have errors in code, the Uses Cleaner cannot continue.
Running on multiple units is more challenging, and on the whole
project still more challenging. In legacy projects, you will be well
advised to run on one unit at a time, or all open units.
As with other wizards in the package, the Uses Cleaner offers
many options, including:
▶ Current Unit: Search current unit.
▶ Opened Units in Current ProjectGroup: Search opened units
in the current Project Group.
▶ All Units in Current Project: Search all units in the current
project.
330 28 CnPack
There are overlaps between CnPack and some other Delphi plug-ins,
but there are more than sufficient capabilities in it for you to make
CnPack a standard part of your toolset.
CodeSite 29
CodeSite (https://raize.com/codesite/) is a very capable 29.1 Details in Your Hands 331
logging tool. You don’t always need logging, but when you do, it 29.2 Data from the Field . 333
saves much time and confusion. As it has been around for quite a
29.3 Simple Example . . . 333
long time, it supports older versions of Delphi—I routinely use it
in Delphi 2007, where it has saved me many headaches.
CodeSite has tools you can deploy with your product, making it
a powerful tool in discovering the root cause of problems which
otherwise can be very difficult to isolate.
The images above are pretty, but it’s always best to begin with a
simple example. Some demo code I wrote for the book is a small
app that offers a Zip code lookup. For the sake of this discussion,
we will focus on a small routine which determines the maximum
number of characters in a city name:
The routine loops through all the records in the dataset, and
for each string in the CityField column, determines whether the
string length is greater than the previously captured maximum. All
simple enough, but a slightly interesting application for logging.
The output in the CodeSite dialog looks like this:
334 29 CodeSite
As there were over 50,000 records in the table, it’s easy to see
that this is much simpler than using the debugger. Trivial as this
sample may be, it should give you some ideas about ways in which
you can make use of CodeSite.
I have made good use of this sort of logging, especially in working
through problems with dynamic layout issues in complex PDF
reports. It is much simpler to obtain such a set of messages, and
then to sit down with the printout and a ruler to untangle the
issues than to battle through it with the debugger. All too often,
you need to obtain a collection of values in order to even begin
an analysis. Logging is not the only possible approach, but it has
been for me a very effective one.
Delphi Mocks 30
Delphi Mocks is an open source tool package from Vincent Parrett 30.1 Delphi Mocks in Use 336
of VSoft Technologies which can be found here: https://github 30.2 Why Mock? . . . . . . 338
.com/VSoftTechnologies/Delphi-Mocks 30.3 When to Mock . . . . 339
As the purpose of mocking is to facilitate unit testing, you may 30.4 Summary . . . . . . . 340
want to revisit this chapter when you have progressed in your
project rework sufficiently to begin assembling a suite of unit tests.
In addition, you are likely to find it very convenient to use DUnitX
as your unit test framework, as Delphi Mocks and DUnitX have the
same author.
Mocks are used as stand-ins for the actual object needed in normal
operation. They are designed to provide simplified stable unit
testing behaviors.
An example could be in database operations. Challenges in testing
against a database include:
▶ Connection to a server
▶ Database content may have changed
▶ Performance of the database will slow testing
At last check, Delphi Mocks requires the use of Delphi XE2 or later.
This requirement is in effect because of language features used
in Delphi Mocks, and though there is a suggestion that perhaps
earlier versions might be supported eventually, there has been no
progress in that direction.
Nick Hodges has written about Delphi Mocks in his book Coding in
[10]: Hodges (2014), Coding in Delphi Delphi.[10]
The home page of Delphi Mocks offers a compact example:
interface
uses
SysUtils , DUnitX . TestFramework , Delphi . Mocks ;
type
{ $M +}
TSimpleInterface = Interface
[ ’ {4131 D033 -2 D80 -42 B8 - AAA1 -3 C2DF0AC3BBD } ’]
procedure SimpleMethod ;
end ;
TSystemUnderTestInf = Interface
[ ’{5 E21CA8E - A4BB -4512 - BCD4 -22 D7F10C5A0B } ’]
procedure CallsSimpleInterfaceMethod ;
end ;
{$M -}
TSystemUnderTest = class (
TInterfacedObject , TSystemUnderTestInf )
private
FInternalInf : TSimpleInterface ;
public
constructor Create (
const ARequiredInf : TSimpleInterface );
procedure CallsSimpleInterfaceMethod ;
end ;
TMockObjectTests = class
published
30.1 Delphi Mocks in Use 337
procedure Simple_Interface_Mock ;
end ;
implementation
uses
Rtti ;
{ TMockObjectTests }
{ TSystemUnderTest }
procedure TSystemUnderTest
. CallsSimpleInterfaceMethod ;
begin
338 30 Delphi Mocks
FInternalInf . SimpleMethod ;
end ;
end .
If the words above didn’t persuade you of the value of mocks, let
me offer more specifically some key points:
▶ Repeatability is key in unit testing.
▶ Isolation from variability is essential to repeatability.
▶ Unit tests should run with minimum overhead.
▶ Unit tests should verify edge cases.
Repeatability is essential as it allows you to verify that your rework
in a module has not produced unexpected errors. You need a suite
of invariant test cases which provide this confidence. Although
some rework will necessitate adding test cases to verify newly
added routines or unforeseen defects not previously caught, this
is a matter of increasing the roster of tests, not starting over.
Isolation from outside factors is the only way you can achieve
repeatable test results. Your unit tests should provide all test
stimuli, and must correctly verify all results produced. A unit
test has no value unless it shows that for input X, the routine
tested always produces result Y. In a perfect world, the code under
test and the unit testing module would be the only participants
in the operation. As soon as another module is introduced, the
complexity of determining the source of a defect is multiplied.
However, in the real world, such perfect isolation may be difficult
to achieve. As in the above references to database operations, you
can’t test them without data. But you can provide fixed data which
is created to exercise thoroughly the logic of code under test.
30.3 When to Mock 339
Bear in mind that repeatability is Outside services such as email or cooperative applications from
not the sole reason to mock database which we obtain input are often more challenging of tests than
or outside providers. Network fail-
ure can leave us helpless to proceed,
a database. Clearly, if our purpose is to verify that our protocol
while using mocks isolates us from module correctly handles an outside provider’s communications,
random acts of the world around us. then we have no choice but to use the real service. But these days,
it has become commonplace for a provider to make available
a well documented set of APIs which serve to isolate us from
implementation issues. A DLL may not be your idea of paradise,
but if you are able to use a third-party provider’s DLL in which
the provider certifies the interface, you are back to the world of
local code operations.
30.4 Summary
UDCs problem than other tools, and being able to see the tree
layout may bring insights. Originally written by Paul Thornton to
solve problems in his own work, the original project has not recently
been updated. However, the project has been forked, and that
version has enjoyed more recent updates. The original on github:
https://github.com/norgepaul/DUDS and a fork on github:
https://github.com/dontenwill/DUDS/tree/master/Docs on
github.
think that unit testing refers to Delphi units, but as the practice
was originated by Java developers, this is clearly not the case.
Think instead of functional units, collections of code which
share a category of application. These may well be congruent
with Delphi units, but that is by no means a rule. Fitting unit
tests to legacy code will often be challenging, and is likely also
to cause you to reconsider the organization of code collections,
as well as the design of classes. The principles presented in the
discussion of SOLID (see Chapter 22), for example, mesh well
with the needs of good unit testing.
Start by selecting Test Project in the wizard and then save the
project. Give some thought to how to arrange your unit test
materials, else you will add substantial clutter as you develop
more and more unit tests. As with any other project, you can
put test projects into project groups. Keep in mind that you will
33.1 DUnit in Recent IDEs 349
program AnalyzeCompsTests ;
{
Delphi DUnit Test Project
-------------------------
This project contains the DUnit test framework
and
the GUI / Console test runners . Add
" CONSOLE_TESTRUNNER "
350 33 DUNIT
{ $IFDEF CONSOLE_TESTRUNNER }
{ $APPTYPE CONSOLE }
{ $ENDIF }
uses
DUnitTestRunner ;
{ $R *. RES }
begin
DUnitTestRunner . RunRegisteredTests ;
end .
Next you will want to open the wizard again and select Test Case.
Again, you will open a wizard with two pages.
Note that it does not matter whether you are building a test project
for a class, a form, or a procedural unit. What the wizard presents
may confuse you into thinking something is wrong, but just carry
on. Wizards help, but are not omniscient.
In the test project, a new unit has been added, and if you look at
the source, it will resemble this:
33.1 DUnit in Recent IDEs 351
unit TestfMain ;
{
Delphi DUnit Test Case
----------------------
This unit contains a skeleton test case class
generated by the Test Case Wizard . Modify the
generated code to correctly setup and call the
methods from the unit being tested .
}
interface
uses
TestFramework , System . SysUtils , Vcl . Graphics ,
Winapi . Windows , System . Variants , Vcl . Dialogs ,
Vcl . Controls , Vcl . Forms , Winapi . Messages ,
System . Classes , fMain ;
type
// Test methods for class TForm1
implementation
initialization
// Register any test cases with the test runner
RegisterTest ( TestTForm1 . Suite );
end .
DUnit Modified
As useful as DUnit is, writing tests can be tedious, and if
you are using Delphi 2010 or later, you may wish to look at
modifications by Stefan Glienke (https://stackoverflow.co
m/questions/8999945/can-i-write-parameterized-tests-
in-dunit/9006662#9006662.) As shown in the linked article,
Stefan has added support for attributes in which you can
specify test cases:
unit MyClassTests ;
interface
uses
MyClass ,
TestFramework ,
DSharp . Testing . DUnit ;
type
TMyClassTest = class ( TTestCase )
private
FSut : TMyClass ;
protected
procedure SetUp ; override ;
procedure TearDown ; override ;
published
[ TestCase ( ’ 2;4 ’)]
[ TestCase ( ’ 3;9 ’)]
procedure TestDoStuff (
Input , Output : Integer );
end ;
implementation
354 33 DUNIT
initialization
RegisterTest ( TMyClassTest . Suite );
end .
Build your first test project to exercise a module which you know
well and believe to be solid. Create test cases which explore
thoroughly the range of operations each method should handle.
You may be surprised—as I was, years ago—to discover that your
solid module has some issues. Some may be obscure corner cases;
others may be embarrassingly major. If your experience with the
first few test projects uncovers defects, you will soon find yourself
committed to the value of unit testing.
Your first unit test code will not be completed in one sitting. No
matter how well you know your code, you are almost certain to
discover the need for tests you had not anticipated. Be patient
with this first round, as you will gain insights and skills in unit
testing, just as in any other facet of programming. You will also
find code which you can’t fully test because a routine is doing too
much—unit testing is a stimulus to better coding.
One reason for starting with familiar code is that devising tests
is initially awkward. It is not usual to try to break your code, but
truly, you need to do just that. It’s great to verify that the code
356 33 DUNIT
does as it should on all the primary cases, but even better when
you are confident that it behaves properly with improper input
values.
You will find that achieving broad coverage of any sizable legacy
project with unit tests is very difficult. The existing code was not
designed with unit testing in mind, and you are certain to find
obstacles in your path:
▶ Routines are too large.
▶ Dependencies are difficult to satisfy.
▶ Forms contain too much business logic.
var
seconds : Integer ;
timefrac : Double ;
begin
for idx := 0 to 84600 - 1 do
begin
timefrac := SecondsToTime ( idx );
seconds := TimeToSeconds ( timefrac );
CheckEquals ( idx , seconds );
end ;
As tests such as this will run very quickly, there is little benefit to
reducing the coverage detail. Testing small utilities is an excellent
starting point because:
▶ The routines are easily understood.
▶ The tests are easily implemented.
▶ Low level routines are widely used, and impact much of your
code.
You must begin implementing unit tests somewhere, and begin-
ning with the low level routines, which we may also think of as
innermost, is not only easy, but helps to establish a solid foundation
for your more complex routines which rely on them.
You will need your unit tests as long as you need your application
code, so they need to be well organized and protected in source
control. Any time you modify a unit for which you have unit tests,
you need to follow a small process:
▶ Run the unit tests on the modified code. Repair code as
needed.
▶ Examine closely your changes to determine whether new or
modified tests are needed. If so, add them, retest, and repeat
till you are confident your unit tests provide full coverage,
and all tests pass or fail as expected.
33.7 Summary 359
33.7 Summary
The more you refactor for testability, the more natural it becomes. Adopting a new methodology is al-
The more unit tests you write, the more natural that becomes. The ways a process. Until it becomes sec-
ond nature, it will feel awkward, and
more defects you find and fix through unit testing, the more you may slow down your development.
will come to rely on it every day. Initially, it may seem like more But in the end, you will gain.
work, but over time, it will actually reduce the work you must do,
especially in defect repair of your application.
Unit testing is proactive; fixing defects from user reports is reactive. Yes, I am cheerleading. I’ve gained
Although customers may take for granted the improved reliability enough benefit from unit testing that
I have no remaining doubts about its
which unit testing brings, they are likely to be very vocal and value.
unhappy over defects which get in their way.
DUnit is easy to use, and very capable. It is mature, but has not
been updated since 2007, so it may lack features which could
provide support to new features of Delphi. If you have not done
much unit testing in the past, it is a great place to begin. Also keep
in mind that is supports Delphi back to before Delphi 7, which
makes it an easy choice for old code.
DUnit2 34
Limited Experience
DUnitX https://github.com/VSoftTechnologies/DUnitX is a
new test framework, taking ideas from DUnit, NUnit and other
test frameworks. It is designed to work with Delphi 2010 or later,
it makes use of language/RTL features that are not available in
older versions of Delphi. Its author, Vincent Parrett, provides this
summary:
Unit Testing in Delphi is not new, the DUnit framework
has been around for many years. So why create a new
unit test framework? My motivation for creating a new
framework was partially frustration with the confusion
over DUnit, DUnit2 and who was maintaining DUnit?
Sourceforge and subversion makes it very difficult to
make contributions to projects. I thought about forking
DUnit and putting it up on GitHub or Bitbucket, but my
feeling was that would make it even more confusing.
Add to that, DUnit was created a long time ago, it’s full
of IfDefs for CLR, LINUX etc, so it’s probably best left
alone (I don’t have Kylix or Delphi.NET installed).
DUnitX supports Delphi 2010 or later. If you need sup-
port for older versions of Delphi, I’m afraid you will
have to stick with DUnit, DUnitX makes use of Gener-
ics, Attributes and Anonymous methods. Delphi 2010
support has some limitations due to compiler bugs.
I work a great deal in Delphi 2007 where DUnitX cannot be used.
I also work in Delphi XE7, and now in Delphi 10.2 Tokyo, where
DUnitX is a viable alternative. However, while bridging these
366 35 DUnitX
Given my own limited exposure to DUnitX I will not try to go very deeply into how it is used, but
the files available on GitHub provide examples such as this:
{ $I DUnitX . inc }
interface
uses
DUnitX . TestFramework ;
type
{ \ $M + }
[ TestFixture ( ’ ExampleFixture1 ’,’ General Example Tests ’)]
TMyExampleTests = class
public
// Run the same test with multiple parameters . ideally we would like to
// implement this using [ TestCase (’ Case 1 ’ ,[1 ,3 ,4]) ] but the delphi
// compiler will not accept arrays of TValue as parameters to attributes ,
// so we have to use a string constant .. the attribute will split the
// string and attempt to convert the values to the parameters of the
// test method .
[ Test ]
[ TestCase ( ’ Case 1 ’,’1 ,2 ’)]
[ TestCase ( ’ Case 2 ’,’3 ,4 ’)]
[ TestCase ( ’ Case 3 ’,’5 ,6 ’)]
procedure TestOne ( param1 : integer ; param2 : integer );
[ Test ]
[ TestCase ( ’ Case4 ’,’ password ="" , password ="" ’)]
procedure TestCaseWithStrings ( const AInput , AResult : string );
[ Test ]
procedure TestTwo ;
[ Test ]
procedure TestError ;
[ Test ]
368 35 DUnitX
[ MaxTime (2000) ]
procedure TooLong ;
// Disabled test
[ Test ( false )]
procedure DontCallMe ;
[ Setup ]
procedure Setup ;
[ TearDown ]
procedure TearDown ;
procedure TestMeAnyway ;
[ Test ]
procedure LogMessageTypes ;
[ Test ]
[ Ignore ( ’ Because I said so !!! ’)]
procedure IgnoreMePublic ;
published
// Because this is a published method , it doesn ’t require the [ Test ]
// attribute [ Ignore (’ Because he said so !!! ’) ]
procedure IgnoreMePublished ;
end ;
[ TestFixture ]
TExampleFixture2 = class
private
FTestsRun : integer ;
public
[ SetupFixture ]
procedure SetupFixture ;
[ TeardownFixture ]
procedure TearDownFixture ;
published
procedure IAmATest ;
end ;
[ TestFixture ]
TExampleFixture3 = class
public
// will be used as SetupFixture
constructor Create ;
// will be used as TeardownFixture
35.1 Looking at Some Code 369
{ \ $M + }
TExampleFixture4 = class
protected
FObject : TObject ;
public
[ SetupFixture ]
procedure SetupFixture ;
[ TeardownFixture ]
procedure TearDownFixture ;
end ;
[ TestFixture ]
TExampleFixture5 = class ( TExampleFixture4 )
public
[ Test ]
procedure Testing ;
end ;
TExampleFixture6 = class
protected
FObject : TObject ;
public
constructor Create ;
destructor Destroy ; override ;
end ;
implementation
uses
{ \ $IFDEF USE_NS }
System . SysUtils ,
System . Classes ,
{ \ $ELSE }
SysUtils ,
Classes ,
370 35 DUnitX
{ \ $IFDEF DELPHI_2010_DOWN }
// D2010 doesn ’t have TThread . Sleep
Windows ,
{ \ $ENDIF }
{ \ $ENDIF }
DUnitX . DUnitCompatibility ;
{ TMyExampleTests }
{ TExampleFixture2 }
{ TExampleFixture3 }
{ TExampleFixture4 }
{ TExampleFixture5 }
{ TExampleFixture6 }
{ TExampleFixture7 }
initialization
TDUnitX . RegisterTestFixture ( TMyExampleTests );
TDUnitX . RegisterTestFixture ( TExampleFixture2 );
TDUnitX . RegisterTestFixture ( TExampleFixture3 );
TDUnitX . RegisterTestFixture ( TExampleFixture5 );
TDUnitX . RegisterTestFixture ( TExampleFixture7 );
374 35 DUnitX
end .
35.2 DUnit vs. DUnitX 375
[ Test ]
[ TestCase ( ’ Case 1 ’,’1 ,2 ’)]
[ TestCase ( ’ Case 2 ’,’3 ,4 ’)]
[ TestCase ( ’ Case 3 ’,’5 ,6 ’)]
procedure TestOne ( param1 : integer ; param2 :
integer ) ;
Often we need to test edge cases and may not benefit from exhaus-
tive tests. Or we may wish to implement absolutely repeatable
tests, often in database-related operations. Being able to define
datapoints for these tests in attributes offers an expressive and
succinct presentation.
35.3 Summary
The warnings:
▶ W501 Empty EXCEPT block
▶ W502 Empty FINALLY block
▶ W503 Assignment right hand side is equal to its left hand
side
▶ W504 Missing INHERITED call in destructor
▶ W505 Empty THEN block
▶ W506 Empty ELSE block
▶ W507 THEN statement is equal to ELSE statement
380 36 FixInsight
37.1 Configuration
As you can see, there are many tools, and quite a few offer custom
configuration.
The second page is called General, and contains only a few entries:
Note, however, that you have control over the font used in the UI,
as well as the few file paths which GExperts needs for operation.
37.1 Configuration 385
The GREP search wizard is one of the items I use frequently. It has
great power and flexibility, and in large projects, is an essential
tool.
What this tool does not provide is any suggestion as to where you
might want to begin your cleanup efforts. Nothing I have found
will do that, nor have I conceived of a strategy to deliver such
suggestions myself.
The Cycles Analyzer uses no third-party components, and can
be built in Delphi 10.3 Rio—the tool I used for it—or in any fairly
recent Delphi, with no changes needed. It would also be fairly
simple to rework for older versions of Delphi, if you desire. The
complete project code is in the downloads for this book: https:
//github.com/wmeyer48/DL-code
Usage is simple, and I have tried to make it obvious. The essential
controls are:
▶ Reference Report edit box. Clicking on the button to the right
of this opens a File Open dialog box. Select your reference
report file and click open. It will be parsed and the results
presented in the grid.
▶ New Report edit box. Works in the same way as the one
for the reference report, but for the second report you will
compare to the first. As with the other, opening the file causes
it to be processed and its data to be added to the grid.
The remaining controls are not essential to operation, but add
functionality you may wish to use, such as searching and exporting
the data to a CSV file. The CSV file may then be imported easily
into Excel for whatever purpose you may find useful.
The form looks like this:
▶ In the AbExcept module row, you can see that the cycle count
is the same for both columns, so zero in the Delta column.
The Cycles Analyzer is a small project which embodies the principles I have been promoting in this
book. The main form code is minimal:
unit u_Main ;
interface
uses
// declarations omitted for the book
type
TfrmMain = class ( TForm )
// declarations omitted for the book
procedure btnExportDataClick ( Sender : TObject );
procedure btnNewFileSelectClick ( Sender : TObject );
procedure btnRefFileSelectClick ( Sender : TObject );
procedure edtSearchChange ( Sender : TObject );
procedure FormCreate ( Sender : TObject );
procedure grdMainMouseMove ( Sender : TObject ; Shift : TShiftState ;
X , Y: Integer );
procedure grdMainTitleClick ( Column : TColumn );
private
FPreviousColumnIndex : Integer ;
FRefCycles : Integer ;
FSortOrder : string ;
function ProcessInput ( const IsRefFile : Boolean ;
const FileName : string ): string ;
end ;
var
frmMain : TfrmMain ;
implementation
{ $R *. dfm }
uses
// declarations omitted for the book
begin
dmMain . ExportData ( dlgExport . FileName , chkIncludeFieldNames . Checked );
end ;
end ;
end ;
procedure UpdateColumnHeader ;
begin
grdMain . Columns [ FPreviousColumnIndex ]. Title . Font . Color := clBlack ;
grdMain . Columns [ FPreviousColumnIndex ]. Title . Font . Style :=
grdMain . Columns [ FPreviousColumnIndex ]. Title . Font . Style - [ fsBold ];
FPreviousColumnIndex := Column . Index ;
Column . Title . Font . Style := Column . Title . Font . Style + [ fsBold ];
end ;
procedure UpdateColumnSort ;
begin
if FPreviousColumnIndex = Column . Index then
if FSortOrder = ’’ then
begin
FSortOrder := ’D ’;
fontColor := clBlack ;
end
else
begin
FSortOrder := ’’;
fontColor := clRed ;
end ;
end ;
begin
UpdateColumnHeader ;
fontColor := clBlack ;
if FPreviousColumnIndex > -1 then
398 38 Homebrew Tools
begin
UpdateColumnSort ;
dmMain . fdmCycles . IndexFieldNames :=
dmMain . fdmCycles . FieldDefs [ FPreviousColumnIndex ]. Name +
IfThen ( FSortOrder . IsEmpty , ’:D ’, ’’);
grdMain . Columns [ FPreviousColumnIndex ]. Title . Font . Color := fontColor ;
dmMain . fdmCycles . First ;
end ;
end ;
end .
The data module is where all the work is done, but it is also relatively light:
unit d_Main ;
interface
uses
// declarations omitted for the book
type
TdmMain = class ( TDataModule )
// declarations omitted for the book
procedure DataModuleCreate ( Sender : TObject );
procedure fdmCyclesCountGetText ( Sender : TField ; var Text : string ;
DisplayText : Boolean );
private
FNewCycles : Integer ;
FNewLoaded : Boolean ;
FRefCycles : Integer ;
FRefFile : Boolean ;
FRefLoaded : Boolean ;
procedure AddToData ( const IsRefFile : Boolean ; const AModuleName : string ;
const ACount : Integer ); overload ;
38.1 Cycles Analyzer 399
var
dmMain : TdmMain ;
implementation
{ $R *. dfm }
uses
System . StrUtils ;
const
NotInRef = -1;
NotInNew = -2;
{ TdmMain }
try
sl . LoadFromFile ( FileName );
if sl . Count > 0 then
begin
if FRefFile then
fdmCycles . EmptyDataSet ;
ProcessLines ( sl );
end ;
if FRefFile then
FRefLoaded := True
else
FNewLoaded := True ;
ProcessFinal ;
finally
sl . Free ;
end ;
end ;
Result := fdmCycles . RecordCount . ToString ;
end ;
fdBatchMove . Execute ;
finally
reader . Free ;
writer . Free ;
end ;
402 38 Homebrew Tools
if IncludeFieldNames then
begin
sl := TStringList . Create ;
try
sl . LoadFromFile ( FileName );
sl . Insert (0 , GetFieldNames ) ;
sl . SaveToFile ( FileName );
finally
sl . Free ;
end ;
end ;
end ;
end ;
begin
fdmCycles . Edit ;
fdmCyclesDelta . AsInteger := _delta ;
fdmCycles . Post ;
end ;
fdmCycles . Next ;
end ;
end ;
end ;
end .
38.1 Cycles Analyzer 405
A better view of the top band after two reports have been loaded.
Here you can see the two paths entered:
As the application is built to be a demo for the book, it uses
only components which ship with Delphi. Some functionality is
38.2 Map Explorer 407
One good resource is the map file Delphi builds. Set the option to
produce the detailed map, and you will find it in the same folder
as your executable. It will be fairly large, but is easy to parse.
408 38 Homebrew Tools
parsing source code, the map file is one of your best sources of
information.
The map file is not documented by Embarcadero, but is based on
an Intel format, and some searching will yield further explanations,
should you need them.
The Map Explorer is included here not for its usefulness, which is
small, but as a seed for ideas you may wish to explore in your own
tools. It illustrates that you cab obtain benefits from very simple
code, when you are able to process an existing simple text file.
By collecting an inventory of all installed components, we can That sounds a bit twisted, but the is-
make use of the information in our collection of the components sue is how to link the components in
our forms with packages from which
actually used in our project. The easiest way to accomplish this they came. The simplest way is to
task seems to be through the use of a Wizard in the Delphi IDE. collect all known components, then
That may seem a daunting task, but there is a very useful starting collect the used components.
point available from David Hoyle, which may be found on his
website: https://www.davidghoyle.co.uk/WordPress/. David
has written a most welcome book explaining the Delphi ToolsAPI
and its application.
Better still for our purposes, David has created a number of instal-
lable applications using the ToolsAPI, and these can be obtained
from his site. The one most suited to our needs in this instance is
his Package Viewer: https://www.davidghoyle.co.uk/WordPres As with other projects, the modified
s/?page_id=1299. This can be modified to produce the inventory code for this is available on my site.
PS . PackageNames [ iPackage ],
TObject ( iPackage ));
if chkExportComponents . Checked then
begin
cdsPackages . Append ;
cdsPackagesRecID . AsInteger := iPackage ;
end ;
ProcessComponents (PS , iPackage , P);
ProcessPackageInfo (P ,
PS . Package [ iPackage ]) ;
if chkExportComponents . Checked then
begin
cdsPackages . Post ;
end ;
frm . UpdateProgress ( Succ ( iPackage ));
end ;
frm . HideProgress ;
finally
frm . Free ;
end ;
tvPackages . AlphaSort ( True );
finally
tvPackages . Items . EndUpdate ;
end ;
Note that the new code in this section is keyed to the testing of
the chkExportComponents check box. This is where we determine
whether the components data is to be collected, though at this
point, it will be, and the check box is not an active feature.
Inside of the ProcessPackageInfo method we have added more
code, to populate the package data into the dataset:
414 38 Homebrew Tools
begin
NN := tvPackages . Items . AddChildObject (N ,
PS . ComponentNames [ iPackage ,
iComponent ], Nil );
if chkExportComponents . Checked then
begin
cdsComponents . Append ;
cdsComponentsRecID . AsInteger :=
cdsComponents . RecordCount ;
cdsComponentsPackageID . AsInteger :=
cdsPackagesRecID . AsInteger ;
cdsComponentsName . AsString := NN . Text ;
cdsComponents . Post ;
end ;
end ;
end ;
end ;
The next step is to collect the list of all DFM files in your project
tree. In recent versions of Delphi, this is a very simple operation:
AddDFMToData (s);
Inc ( count );
end ;
Result := count ;
end ;
Simple as it is, the utility of the resulting list is low, as we get only
an unambiguous filename for each of the files. As it will be helpful
in other processing to have these filenames in a dataset, we will
add a routine to parse the strings and fill the dataset, adding a
unique numeric ID to each record. Although it is mostly concerned
with adding to a dataset.
Now that we have a list of all the DFM files, we can scan them
for instances of components. This process is simple: We will scan
each DFM file looking for lines in which the first token is object
These data will accumulate to a dataset in which each record
contains a foreign key for the form, as well as a primary key for
the component type. The records will also contain an instance
count. After collecting these data, we will also populate a dataset
in which we will have one record per component type, with the
count of all instances of that type in the application.
38.3 Component Collector 417
Here you can see the list of DFM files is passed in, and we then
iterate through the list, calling GetUsedCompsFromForm(s); on each
form s:
begin
FUsedCompTypes := 0;
38.3 Component Collector 419
FMaxTypesPerForm := 0;
sl := TStringList . Create ;
try
if FileExists ( AForm ) then
begin
cdsDfmFiles . DisableControls ;
try
cdsDfmFiles . IndexFieldNames := ’ UFN ’;
found := cdsDfmFiles . Locate ( ’ UFN ’, AForm ,
[ loCaseInsensitive ]) ;
finally
cdsDfmFiles . EnableControls ;
end ;
sl . LoadFromFile ( AForm ) ;
for s in sl do
begin
if s. Contains ( ’ object ’) then
begin
arr := s. Trim . Split ([ ’ ’]) ;
if Length ( arr ) = 3 then
AddCompToData ( arr [2] , found )
else
AddCompToData ( ’ DFM Error ’, found );
end ;
end ;
end ;
finally
sl . Free ;
end ;
end ;
try
Result := -1;
if cdsComponents . Locate ( ’ Name ’, AComp ,
[ loCaseInsensitive ]) then
if cdsPackages . Locate ( ’ RecID ’,
cdsComponents . FieldByName (
’ PackageID ’). AsInteger , []) then
Result :=
cdsPackages . FieldByName (
’ RecID ’). AsInteger ;
finally
cdsComponents . EnableControls ;
cdsPackages . EnableControls ;
end ;
end ;
sl . LoadFromFile ( AForm );
for s in sl do
begin
if s. Contains ( ’ object ’) then
begin
arr := s. Trim . Split ([ ’ ’]) ;
if Length ( arr ) = 3 then
AddCompToData ( arr [2] , found )
else
AddCompToData ( ’ DFM Error ’, found );
end ;
At this point, there is much value in our datasets, but we still need
to aggregate the per file component data into a combined dataset
to obtain the full application view.
cdsCompTypes . Close ;
cdsCompTypes . CreateDataSet ;
cdsUsedComps . First ;
while not cdsUsedComps . Eof do
begin
cdsCompTypes . Filter := ’ Name = ’ +
QuotedStr ( cdsUsedCompsName . AsString );
cdsCompTypes . Filtered := True ;
if cdsCompTypes . RecordCount = 1 then
begin
cdsCompTypes . Edit ;
cdsCompTypesInstances . AsInteger :=
cdsCompTypesInstances . AsInteger +
cdsUsedCompsInstances . AsInteger ;
end
else
begin
cdsCompTypes . Filtered := False ;
cdsCompTypes . Append ;
cdsCompTypesRecId . AsInteger :=
cdsUsedCompsRecID . AsInteger ;
422 38 Homebrew Tools
cdsCompTypesName . AsString :=
cdsUsedCompsName . AsString ;
cdsCompTypesInstances . AsInteger :=
cdsUsedCompsInstances . AsInteger ;
end ;
cdsCompTypes . Post ;
cdsUsedComps . Next ;
end ;
cdsCompTypes . Filtered := False ;
As this project in its entirety is far outside the scope of this book, I
will not further develop the application here. I have demonstrated
the development of elements which could be used in a number of
ways to harvest useful information from your project source tree. In
the current context, the problem is that each level of developments
triggers further ideas, and the project scope explodes. My purpose
is the book, not a huge project, so we will move forward at this point,
and I may consider developing the project fully as a foundation
for another book.
▶ Collect from the map file all of the modules which are
contributing to the EXE. You will want to merge that info
with the data from the file tree, to begin to identify modules
which may no longer be needed.
▶ At this point, another possible strategy would be to add code
that can go through the project tree and prepend to each pos-
sibly unused module name a simple prefix, such as a double
underscore. Those modules will then be unknown to your
project. If you are able to build, then these renamed mod-
ules are not needed. Of course, if your source tree contains
multiple projects, then things are more complex.
MapFileStats 39
MapFileStats https://www.delphitools.info/other-tools/ma
pfilestats/ is a small but very effective tool written by Eric
Grange, to whose site the link will take you.
Here, for example, we drill into the Delphi StrUtils library, and see
the small number of routines which are used from that:
However, as always, there is bad news to offset the good. Map-
FileStats is a standalone tool, published in executable form, and
source code is not available. As interesting as it may be for explor-
ing a map file, my experience has been that in legacy projects, it is
426 39 MapFileStats
The various Add operations have the particular benefit that you
may invoke them wherever you are in the class, and they will do
their work without your needing to go elsewhere in the code. The
428 40 MMX
It’s not that adding a class is a difficult thing, but using the wizard
from MMX makes the result more consistent, and it may help
you to remember details which might sometimes otherwise be
overlooked. The Add Class wizard is a simple and clean way to
ensure consistency in the details of your coding. Details can make
or break your programming, and the wizard helps with those. You
will see this, or similar:
Adding a field is simple, but with different details than for a class,
and once again, the wizard makes it more likely to be consistent
in your coding.
430 40 MMX
where teams will be active now and for the foreseeable future. I
contend that asserting a set of options for the project is a benefit,
even when there may be team members who dissent from the
standard choices. Finding things where you expect them to be, in
unit after unit, is really helpful.
You place the cursor in the unit specifier, and use Ctrl-Alt-Shift-
Up/Dn. Note that when you are using RDP
to access a remote development en-
vironment, this hot key combination
is not functional. You may, however,
40.7 Unit Dependency Analyzer assign a different hot key to the oper-
ation.
AbUtils,AbExcept,AbUtils
40.8 Summary
MMX is such a rich tool that I can hope only to provoke your
interest enough that you will try it. Any attempt at complete
documentation would lead to the production of another book, and
is thoroughly impractical in this volume.
Peganza Products 41
41.1 Overview . . . . . . . 435
41.1 Overview
41.2 Pascal Analyzer . . . 436
41.3 Pascal Expert . . . . . 438
Peganza (https://peganza.com) is a Swedish software company
which specializes in tools for Delphi developers. At this writing, 41.4 Pascal Browser . . . . 439
they offer three products:
▶ Pascal Analyzer, which does what its name suggests, and was
first released in 2001.
▶ Pascal Expert, which is a plug-in to Delphi and provides
analytical operations inside the IDE. Released in 2015.
▶ Pascal Browser, which is a documentation tool, and was re-
leased in 2007.
▶ Pascal Analyzer Lite, a reduced version of Pascal Analyzer,
available for free.
Peganza offers trial versions of its products, making it very easy to
explore what they can do for you.
Pascal Analyzer provides some analyses not available from other
tools. In particular, it is the only tool I have used which attempts to
identify unit references in the interface uses clause which can be
demoted to the implementation uses clause. I say attempts because
I have found some cases in which the demotion is not possible,
but these soon become familiar, and as any action taken requires
human intervention, are easily left alone.
Pascal Expert provides information about your code which it
presents in the Messages pane in the IDE. It spirit, it is similar to
FixInsight, though it appears to cover a broader range of concerns.
The documentation declares that the results match those obtained
form Pascal Analyzer, but of course, with greater immediacy than
through the external operation of Pascal Analyzer.
Pascal Browser is a tool which analyzes all your code modules in a
project and creates from them a collection of html files—possibly
a massive collection—which you can then browse or use as a
starting point for creating a developer’s manual.
436 41 Peganza Products
Peganza has offered the Pascal Analyzer since 2001, and it can
provide an overwhelming collection of analytical information
about the state of your code. One report I have found most useful
flags the units in interface uses clauses which could be demoted
to the implementation uses clause. It is the only tool I have used
which can do this.
Used units:
System source not found
Forms source not found
fMain in implementation
==> FormLogic unnecessary
==> uInterfaced unnecessary
The FormLogic and uInterfaced members of the interface uses
clause are not needed, and may be removed.
41.2 Pascal Analyzer 437
Again, this is a tool which you will be unlikely to run often, but its
capabilities will give you a great start toward creating developer
documentation.
End of rant.
extract from the input: Zip, City, and State. Note that the input
file contains multiples of some Zip codes where these are used for
multiple cities. The program copies only unique Zip/City pairs,
so there will be multiple copies of some Zip codes, but far fewer
than in the input file.
Using the 32-bit version of ProDelphi, and profiling the 32-bit
version of the ZipCodes project yields this result:
finally
DataSet . Active := wasActive ;
end ;
finally
lst . Free ;
end ;
finally
writer . Free ;
end ;
end ;
program Project1 ;
uses
TestInsight . Client ,
TestInsight . DUnit ,
GUITestRunner ,
SysUtils ;
end ;
begin
if IsTestInsightRunning then
TestInsight . DUnit . RunRegisteredTests
else
GUITestRunner . RunRegisteredTests ;
end .
Now lets look at a small sample program which you can download
from the repository for this book. The program is very similar to
one which Stefan Glienke used in a youtube video on TestInsight
in 2015. First, let’s look at the main code unit, where the test class
is declared.
unit u_Test ;
interface
uses
DUnitX . TestFramework ;
type
[ TestFixture ]
TwmTestObject = class ( TObject )
public
[ TestCase ( ’1 and 1 ’, ’1 ,1 ,2 ’)]
[ TestCase ( ’1 and 2 ’, ’1 ,2 ,3 ’)]
[ TestCase ( ’2 and 2 ’, ’2 ,2 ,4 ’)]
451
var
wmTestObject : TwmTestObject ;
implementation
uses
u_UnderTest ;
initialization
TDUnitX . RegisterTestFixture ( TwmTestObject );
end .
unit u_UnderTest ;
452 43 TestInsight
interface
implementation
end .
Clearly, this does not resemble tests you are likely to write for
your projects. Yet we must begin somewhere, and it is useful to
present the simplest of code if it can still illustrate the process. You
may wish to add functions for subtraction, multiplication, and
division.
Now when we run the test in TestInsight, the first test passes
because 1 + 1 = 2. The other four tests of addition fail, because the
expected results are always something other than 2:
453
With this simple program in place, you can explore the behaviors
of the TestInsight Explorer very easily. It is a replacement for the
DUnit GUIRunner, but it is more. The icons at the top of the
Explorer allow you to alter the presentation. If, for example, we
change from the default of displaying result by type to displaying
by fixture, we see this:
[1] Walter S. Brainerd. Pascal Programming: A Spiral Approach. Boyd & Fraser Pub. Co., 1982.
[2] Julian Bucknall. ‘Creating Easily Testable User Interfaces’. In: The Delphi Magazine (Aug.
2005).
[3] Delphi Component Writer’s Guide. Borland Software Corporation, 1995.
[4] Michael C. Feathers. Working Effectively with Legacy Code. Prentice Hall, 2004.
[5] Martin Fowler. Refactoring. Addison-Wesley, 1999.
[6] Primož Gabrijelčič. Delphi High Performance. Packt, 2018.
[7] Primož Gabrijelčič. Hands-On Design Patterns with Delphi. Packt, 2019.
[8] David Gries. Compiler Construction for Digital Computers. John Wiley and Sons, 1971.
[9] Eric Harmon. Delphi COM Programming. Macmillan Technical Publishing, 2000.
[10] Nick Hodges. Coding in Delphi. Nepeta Enterprises, 2014.
[11] Nick Hodges. Dependency Injection in Delphi. Trenchant Publishing, 2017.
[12] Donald E. Knuth. ‘Structured Programming with goto Statements’. In: Computing Surveys
(Dec. 1974).
[13] Bertrand Meyer. Object-Oriented Software Construction. Prentice Hall, 1988.
[14] Charles Petzold. Programming Windows®, Fifth Edition (Developer Reference). Microsoft Press,
1998.
[15] Dalija Prasnikar and Neven Prasnikar Jr. Delphi Memory Management. CreateSpace Indepen-
dent Publishing Platform, 2018.
[16] Nicklaus Wirth. Algorithms + Data Structures = Programs. Prentice Hall, 1976.
Alphabetical Index