Delphi Programming - Real programmers write comments mostly in or about other peoples code.
Delphi Programming
and software in general.Friday, December 12, 2008
Review: Framework Design Guidelines, 2nd Ed. by Krzysztof Cwalina and Brad Abrams
When you write code with the intent of making it public and reusable, there are certain aspects of designing classes, interfaces, types and so forth, where we really need experience to come up with easy to understand and easy to use code. This book delivers 6 years worth of wisdom and experience from the .NET teams and we can all do well by picking their brains for reusable knowledge.
The book present a sensible set of lessons learned and goals to strive for. Without lecturing, and always exemplifying - we get a step by step journey through the different aspects of designing a framework. It is concisely written and easy to read, and it is not without humor.
The book is richly commented by the developers themselves, embellishing on the guidelines, sometimes including design regrets and mistakes made, and sometimes also hinting towards why you might want to break a guideline under certain conditions.
As Anders Hejlsberg write in the foreword: "The guidelines have served us well through three versions of the .NET Framework and numerous smaller projects, and they are guiding the development of the next generation of APIs for the Microsoft Windows operating system".
I won't delve into all the details of the content, but it covers the basics of how to design a framework by being roleplaying the consumer as well as testing it on your peers, good naming, type design, member design, extensibility, exceptions, as well as usage guidelines. It rounds off with a collection of common patterns such as aggregate components, async patterns, dependency properties, dispose pattern, factories, and many more. There is also a brief C# coding style convention section and a tutorial on using FxCop to enforce the framework design guidelines.
The book is not C# centric, but embellish the fact of .NET being a multi-lingual platform, and remind you to avoid certain constructs that may be too tightly bound to a specific language.
"Framework Design Guidelines (2nd Ed.)" by Krzysztof Cwalina and Brad Abrams (Addison-Wesley ISBN-13: 978-0-321-54561-9) falls under the collection of books that I wish I had available to read when I started out coding. Together with "Code Complete (2nd Ed.)" by Steve McConnell, it should be mandatory reading for every developer, regardless of language or development niche.
Highly recommended.
Tuesday, November 18, 2008
Porting from D5 to D2009 - prologue
300.000+ lines of code, using numerous third party components, a sophisticated in-house ORB sitting on top of BDE with several designtime components, built-in version control for SQL statements and tight Oracle integration. Dozens of input forms and hundreds of reports.
I am not quite sure where to begin...
Should I convert to D2007 first, or go directly for D2009?
Advice would be appreciated.
Monday, October 27, 2008
FDCLib - Wizards - On hold for now
Thursday, October 16, 2008
Windows 7 - How it was built
A part that made me chuckle, was: "This is where one of the big differences between Vista and Windows 7 occurs: In Windows 7, the feature crew is responsible for the entire feature. The crew together works on the design, the program manager(s) then writes down the functional specification, the developer(s) write the design specification and the tester(s) write the test specification. The feature crew collaborates together on the threat model and other random documents. Unlike Windows Vista where senior management continually gave “input” to the feature crew, for Windows 7, management has pretty much kept their hands off of the development process."
What I find most interesting is how the article ends - where the choice of words indicate that Windows 7 already is feature complete.
Tuesday, September 30, 2008
Swindell: Native code remains the core foundation of application development
Full article here: http://www.ddj.com/architect/210604499
It's good to be a Delphi developer.
Edit: I guess this is an infomercial :P - Well, I bought it :)
Wednesday, September 10, 2008
Snippet: Convert Java time to TDateTime
First, I convert it to a Windows filesystem time - which is similar to the Javatime, except from the start time offset and the time resolution. Next, I convert the filetime to Windows UTC time, and adjust for the timezone - simplified to just use the default - ie current local timezone. The final step is to convert the system time constant to a TDateTime.
The whole thing is done with blatant disregard of the tradition of checking return values from the two system calls. The less optimistic of you may choose to check for return values equaling zero to indicate an error.
FYI: 11644473600000 * 10000 = 1 Jan 1970 in Windows File Time
uses
Windows, SysUtils; // Time functions
function JavaTimeToDateTime(javatime:Int64):TDateTime;
// java time -> Win32 file time -> UTC time
// adjust to active time zone -> TDateTime
var
UTCTime, LocalTime: TSystemTime;
begin
FileTimeToSystemTime(TFileTime(Int64(javatime + 11644473600000) * 10000), UTCTime);
SystemTimeToTzSpecificLocalTime(nil, UTCTime, LocalTime);
Result := SystemTimeToDateTime(LocalTime);
end;
Importing Delphi from UK to Norway
After checking with the Customs Authorities in Norway, they confirmed that there is no Norwegian import tax on software.
If the software delivered on physical media,you have to pay 25% VAT to the Customs Authorities with the import declaration.
If the software is digitally delivered, the Customs Authorities don't require you to pay them the 25% VAT, although if you are required to register the cost in your balance sheets, you will need to clarify with your accountant/auditor and/or the regional tax office on how it should be registered.
For a hobbyist, that means you effectively get the software at net cost from the reseller and at the same rate as the rest of the world.
Although this solves the problem with the inflated price of Delphi on the Nordic CodeGear web shop for the consumer - I can't help but to ponder what kind of problem this pose for the local resellers. They would have to be able to offer some seriously good support to compensate for the inflated price they are required to charge.
Tuesday, August 26, 2008
D2009 Euro Markup Correction: 31% (not 47%)
I should have taken a hint from my own comments in the first article... sheesh...
Still - fact is that the EUR price for Nordics still is 31% above the USD price. That is way out of wack.
You can see the calculation here.
The true cost difference for the Embarcadero webshop prices in Norwegian Kroner is shown in the rightmost blue columns.
More egg on my face at CodeGear forums: https://forums.codegear.com/thread.jspa?threadID=1589
Delphi 2009 Pricing - Euro Markup = 31%
I made a horrific mistake (worksheet formula cut and paste with wrong cell reference). The correct ratio is 31%, not 47%.
I should have taken a hit from my own comment on the 1.31 ratio... sheesh...
Worksheet updated.
Obviously the 1 USD = 1 EURO assumption was completely false.
-- end edit --
Embarcadero / Codegear are still treating 1 USD = 1 EURO. In Norway, the exchange rates give a markup ratio of 1.47 times. Personally, I think that is rather stiff. You can see the calculation here.
The middle example with the 1.31 ratio is due to Norwegian EUR/USD currency exchange margins (DnB NOR Bank). The true cost difference for the Embarcadero webshop prices in Norwegian Kroner is shown in the rightmost blue columns.
Tentative prices from one local reseller indicates a markup ratio of 1.33, comparing reseller out price to US webshop price. Why does this feel like being ripped off? When buying software online, I am used to getting the same price as the rest of the world.
Posted on CodeGear forums: https://forums.codegear.com/thread.jspa?threadID=1589
Tuesday, August 19, 2008
FDCLib - Wizards without the black magic - part 1
Frames are great, but they can require a bit of fiddling to work well. The plan is to create a simple system to organize frames for tabbed views and wizards and simplify some of the design problems related to visual inheritance as well as class inheritance.
Design goals
1a. Each frame must not need to descend from the same class
1b. It must still be possible to extend frames by inheritance
2a. Each frame should not need to contain extensive knowledge about other frames
2b. Each frame must be able to obtain knowledge about/from other frames
3. Each frame should have (optional) input validation
4. It must be possible to make reusable frames such as file pickers, etc.
5. It must be possible to use the frames in different contexts (modal or modeless, window or dialog)
Using a similar approach to what I did for the grid controller, I will wrap myself around a basic TFrame and grab hold of the hooks that exist and move the business logic into a non-visual class known as TFrameWrapper.
To organize these non-visual classes, I will create a TFrameController. The frame controller will hold the knowledge about how the frames are to be presented, organized, and navigated.
Teaser: The frame controller will be reused in a future part of FDCLib. What part of the application do you usually need first, but often end up writing last? No, not the documentation :P - we are talking about code after all :)
Before I move on - I'd like gather some intel about how you are using frames today.
Your comments will be greatly appreciated!
Friday, August 15, 2008
Google Custom Search and Delphi
I have attempted to add some Delphi relevant sites to the index, and currently have configured the search to (Edited) allow hits outside only the specified sites. It would be useful to explicitly add more sites, but this is just an experiment.
Give it a try and see if you get what you expect...
P.S. AdSense is NOT enabled, and I will NOT enable it. Ideally, Embarcadero should create and maintain the custom search, allowing it's community to contribute.
Edit: Please not that this was a very quick and dirty configuration. Given more time and experience with the CSE, it should be possible to get better results.
Tuesday, August 12, 2008
repeat Mistake until LessonLearned;
Coding starts with an idea, some thinking about implications, hopefully some deliberation on requirements, possibly the choice of some pattern for implementation and maybe even some kind of plan for testing that it all work according to plan. Even if we put all of the above into action, we will most likely repeat some common coding mistakes.
"[A]nd then it occurred to me that a computer is a stupid machine with the ability to do incredibly smart things, while computer programmers are smart people with the ability to do incredibly stupid things. They are, in short, a perfect match."
~Bill Bryson
Here are some of my more typical causes of involuntary visits in the debugger.
I know that many of my mistakes stem from the brain being ahead of the fingers in the typing process. I have the idea all figured out in my head, and now I want to see it in action. In my desire to "save time" - I convince myself that I can add the checks later, but for now I can get away with putting up just the basic scaffolding and leave the safety rails for later. Naturally, when later comes - I am already on a different problem, taking the same shortcuts.
It boils down to bad habits.
Bart Roozendaal wrote about pre and post-conditions yesterday, and although these may save my bacon to a certain degree - it still boils down to the same issue: i.e. my failure to take time to actually implement the safety measures immediately, and instead postponing them until later.
So... what should I do to catch my most common mistakes? Pick up some good habits and stick with them. Time spent early will be saved ten-fold later.
Here are some suggestions:
• Forgetting to check for assignment (NIL pointer) and Invalid typecasts
Assert. Assert. Assert. Make sure that you qualify every reference before you use it.
In the context of Bart's post, maybe pre/post requirements would be even more useful if they would generate tips (lightweight hints?) at the location where the method (which have the requirements) is called.
• Dangling pointer (Non-NIL, but invalid)
FreeAndNil(ObjectRef); Just do it. Avoid passing and keeping non-volatile copies of the reference. If you really need the reference, implement some sort of common storage that encapsulate the object(s) you need to reference i.e. create a secure reference to a reference. Keep in mind that the such common storage should exist before and after the life of which ever object it references.
• Index/access out of string/array bound
Take a minute and check the boundary cases - what happens at the minimum and maximum index? Will index +/- length exceed the boundary?
• Numeric value or enumeration value out of range, Boolean evaluation mistake, Numeric evalution mistake (implicit cast accuracy problem)
Be explicit. Adding more brackets and explicit casts will not slow down your code. Break down the expression logic into smaller parts for clarity if you need to.
One book that is a veritable goldmine of healthy coding advice is Code Complete (2nd ed.) by Steve McConnell. The book's advice is generally language agnostic, and you will most likely recognize much of the advice given as common sense - but the real value is to use the book to remind yourself of using that common sense. If I had read this book as a student, I would have saved myself a lot of trouble.
If you don't wanna shop for books - do as Lars D and explain the code to your favorite Teddybear.
"What I mean is that if you really want to understand something, the best way is to try and explain it to someone else. That forces you to sort it out in your own mind. And the more slow and dim-witted your pupil, the more you have to break things down into more and more simple ideas. And that's really the essence of programming. By the time you've sorted out a complicated idea into little steps that even a stupid machine can deal with, you've certainly learned something about it yourself."
~Douglas Adams
Monday, August 11, 2008
Optimization - Understand your hardware
The generally recognized and recommended approach to optimization is that you never optimize code until you really must. The reasoning behind this is that you need actual running code before you can pinpoint the real bottlenecks and there is no point in spending time on optimizing code that is run once every blue moon, since you hardly will have any significant gain in performance.
The Delphi compiler is very good at optimizing code. Most of the time, you would be hard pressed to write better assembly code than what the compiler produces.
However, we are the ones that put down the premises for how it will organize and access data. If we tell the compiler to work the rows or the columns, the compiler generally will have to do what we say. We decide data size, data organization, and data access. The best kind of optimization we can do is to think hard about how we organize and access our data.
Once upon a time, before computers became a household appliance, around the time when the computing jungle still reeked of digital dino-dung, I decided to pursue a career in designing microprocessors. This was before I realized that it was - relatively speaking - cheaper to make mistakes in software than in hardware.
I learned some useful lessons on accessing data the hardware way, though. Accessing memory is costly! Very costly! You should avoid it if you can! :)
The ridiculously simple example is that if you store your data in rows, it will potentially be very costly to access it column by column. If you need to process large amounts of data in memory (array / matrix / SSD type operations), the size and alignment of your data matter more than you think.
Here is a small project that you can play around with to see effects of data alignment. The demo allocates 10.000 items at the size of 512 bytes as one block, and adds an offset (0 .. 25) from the original block start to the item address. Each block gets a key value from a Random double; Random seed is reset for each offset iteration. Each fill and sort is repeated 5 times to try to avoid random interference from the OS etc. The "read and write" of the data for sorting, doesn't actually move all the data, just the key (Double) and a simulated move of the block size into a local buffer. A fullblown sort with data move would add a few move moves, which would add more boundary overhead.
This is average fill+sort time (raw TDateTime value). Notice the difference on 8 byte boundaries.
Well known C++ guru, Herb Sutter held a presentation "Machine Architecture: Things Your Programming Language Never Told You" for the North West C++ Users Group. He can convey this knowledge far more entertaining and far more effective than I can. BTW, he also have several interesting articles on concurrency.
To quote the notes on the video: High-level languages insulate the programmer from the machine. That’s a wonderful thing -- except when it obscures the answers to the fundamental questions of “What does the program do?” and “How much does it cost?” The C++/C#/Java programmer is less insulated than most, and still we find that programmers are consistently surprised at what simple code actually does and how expensive it can be -- not because of any complexity of a language, but because of being unaware of the complexity of the machine on which the program actually runs. This talk examines the “real meanings” and “true costs” of the code we write and run especially on commodity and server systems, by delving into the performance effects of bandwidth vs. latency limitations, the ever-deepening memory hierarchy, the changing costs arising from the hardware concurrency explosion, memory model effects all the way from the compiler to the CPU to the chipset to the cache, and more -- and what you can do about them.
Herb Sutter is an excellent speaker, so find a quiet spot, pull out the popcorn, and set aside 1 hour and 56 minutes for a very entertaining and very enlightening session on the true cost of accessing memory.
Video link: http://video.google.co.uk/videoplay?docid=-4714369049736584770
Thursday, August 7, 2008
Writing Readable Code - Paul's Snippet Rewritten
I reformatted Paul's example using two spaces for every indent level (after then, begin, else, etc. ), hoping that this is the way he originally wrote it.
IMO, Paul's formatting (or my assumptions about his formatting) does not reflect the true code path, and trying to decipher the code paths become unnecessarily hard.
tryHere is a small exercize. If we say that the string "ABCDEFG" are all conditions TRUE, and the string "abcdefg" are all conditions false:
if ConditionA then
Code(1)
else if ConditionB then
if ConditionC then begin
if ConditionD then
code(2);
code(3);
end
else if ConditionE then begin
code(4);
morecode('X');
if ConditionF then
code(5)
else if ConditionG then
code(6);
end else begin
code(7);
morecode('Y');
end;
finally
code(8);
end;
• What codes are run by "ABCDEFG"?
• What codes are run by "aBcdefg"?
• What string(s) would make code(6) run?
TS contributed a very nicely formatted version, which is effective in guiding us through the potential code paths. I like his style.
try
if ConditionA then
Code(1)
else if ConditionB then
if ConditionC then
begin
if ConditionD then
code(2);
code(3);
end // without the semi-colon
else if ConditionE then
begin
code(4);
morecode('X');
if ConditionF then
code(5)
else if ConditionG then
code(6);
end
else
begin
code(7);
morecode('Y');
end;
finally
code(8);
end;
SS didn't quite manage to reflect the flow in the code and his formatting is similar to Paul's code.
try
if ConditionA then
Code(1)
else if ConditionB then
if ConditionC then
begin
if ConditionD then
code(2);
code(3);
end //; I assume this semi-colon has to go
else if ConditionE then
begin
code(4);
morecode('X');
if ConditionF then
code(5)
else if ConditionG then
code(6);
end
else
begin
code(7);
morecode('Y');
end;
finally
code(8);
end;
Jolyon scores high on restructuring and simplifying, but he made one mistake in his change. Under which condition will his code behave differently form the original?
procedure WhenC;
begin
if ConditionD then
code(2);
code(3);
end;
procedure WhenE;
begin
code(4);
morecode('X');
if ConditionF then
code(5)
else if ConditionG then
code(6);
end;
begin
try
if ConditionA then
Code(1)
else if NOT ConditionB then
EXIT;
if ConditionC then
WhenC
else if ConditionE then
WhenE
else
begin
code(7);
morecode('Y');
end;
finally
code(8);
end;
AO goes even further than Jolyon in reformatting and comments: As I believe that proper formatting is not the only way to increase readability of code, I also refactored it a bit to reduce nesting where possible. If this were an actual code, I would have probably gone even further and introduced separate routines for the individual blocks, depending on their complexity.
try
{ This comment explains why ConditionA is handled first. }
if ConditionA then begin
Code(1);
Exit;
end;
{ This comment explains why the aggregate of ConditionB and ConditionC is handled next. }
if ConditionB and ConditionC then begin
{ This comment explains why ConditionD is relevant only in this block. }
if ConditionD then
code(2);
code(3);
Exit;
end;
{ This comment explains why ConditionE is handled next. }
if ConditionE then begin
code(4);
morecode('X');
{ This comment explains why ConditionF and ConditionG are relevant only in this block. }
if ConditionF then
code(5)
else if ConditionG then
code(6);
Exit;
end;
{ This comment explains the default handling. }
code(7);
morecode('Y');
finally
{ This comment explains why the following code must execute no matter what. }
code(8);
end;
This is readable, but personally I think AO went a bit too far. Like Jolyon, he also missed a structural detail in the refactoring and broke the code. Which condition state(s) will cause the code to misbehave?
I am divided on the use of Exit. It can add clarity, but it can also be a big problem as you leave a lot of code "dangling". If you decide to move the exit - you have to be really careful to ensure that any states that suddenly move in or out of scope behave correctly. If there is a nesting church and a chunking church, I'm probably a "Nestorian".
I do agree that refactoring is a valuable tool to clarify and simplify, and we should make an effort to break down our code into manageable blocks, but in this particular case it probably isn't necessary.
Another thing: I avoid using {curly braces} for in-code commentary and use // instead. Why? Because if you need to comment out code containing select count(*) from table, those curlies will work where the (* comment *) fail. Should CodeGear add support for comment nesting? I don't know...
Anyways...
Here's how I would format the example. This is very similar to TS's example, except that I showel all the reserved words to the left side to leave the logic more visible and commentable, but I also add indentation to the innermost conditional code.
try
if ConditionA
then Code(1)
else if ConditionB
then if ConditionC
then begin
if ConditionD
then code(2);
code(3);
end
else if ConditionE
then begin
code(4);
morecode('X');
if ConditionF
then code(5)
else if ConditionG
then code(6);
end
else begin
code(7);
morecode('Y');
end;
finally
code(8);
end;
MJ adds a contribution with the following comment: "To be sure not to create any future bugs you should consider adding a begin end section after every if statement even if it is not required, but that will make the code more unreadable."
try
if ConditionA then
Code(1)
else if ConditionB then
begin
if ConditionC then
begin
if ConditionD then
code(2);
code(3);
end
else if ConditionE then
begin
code(4);
morecode('X');
if ConditionF then
code(5)
else if ConditionG then
code(6);
end
else
begin
code(7);
morecode('Y');
end;
end;
finally
code(8);
end;
Good Points! In all honesty, I also screwed up the code blocks on first try. Conditional code will bite you if you are not very very careful. You should indeed think about what may happen if you need to add more code and/or conditions. Personally, I don't think a few more enclosures makes the code less readable. Here is how I would be more explicit in the use of enclosures to make the code less ambiguous.
try
if ConditionA
then Code(1)
else begin
if ConditionB
then begin
if ConditionC
then begin
if ConditionD
then code(2);
code(3);
end
else begin
if ConditionE
then begin
code(4);
morecode('X');
if ConditionF
then code(5)
else if ConditionG
then code(6);
end
else begin
code(7);
morecode('Y');
end;
end;
end;
end;
finally
code(8);
end;
There is no one true correct way of formatting.
The point I am trying to make is that code structure matter for understanding the code at first glance, and we should be mindful about how we lay it out. We should strive for consistency, but always keep clarity and unambiguity as priority one. Bend your rules, if you need to.
Paul, Thank you for creating such a devious code snippet!
P.S. If you haven't figured out the answers yet, load up your Delphi and run all the examples in StructureDemo.dpr.
No peeking until you have some suggestions! :)
(Hint: else starts a new block)
Edit: Added an example of using Exits instead of nesting to the initial post on formatting. It would be interesting to see DelphiFreak have a go at Paul's snippet.
Writing readable code - Comment for Context
It is said that "Real programmers don't write comments - It was hard to write - it should be hard to read". That doctrine is way overdue for deletion.
Comments are the context frame of our code.
Too little and the result is bland and unpalatable, too much and it stinks things up. Comments should only in rare occasions assume that the reader is too dumb to read the code properly, so as a general rule - we should not rewrite our code in plain english, line for line. Such an approach tend to fog up [sic] the source, and it becomes a drag to maintain (Yeah, I know... garlic metaphors stink...).
Comments are like news headlines.
// File ready for saving!
// No more filehandles, says OS
// Raises exception for World+dog
They should be short, concise and to the point. In good tabloid tradition, they should also only touch on the very general points, and oversimplifying what really is going on.
Comments are like foreplay.
They serve to get us readers in the mood to appreciate the sleek lines, the richness of it's properties, and possibly arouse our interest to the point where we are ready to ravage the code.
Comments are like, bi-se... uh directional.
Err, well ... what I am trying to say is that sometimes we can write them before we do stuff, while at other times - it can be just as effective to write them after something has been done in the code. The first would probably indicate how we are going to do something, while the latter would focus on the result of what we just did.
Pick up the garbage!
Do you leave your workshop tools lying about in case you need them quickly, or do you store them safely away? Once you finalize your code, clean it up. Put "I deleted this, moved that, added that" comments in your version control commit/check-in comments, and don't litter the source code with it. Once the code has been created or removed, the reason is uninteresting. What the code is supposed to do, is so much more valuable information. Remove unnecessary comments.
Don't spread FUD.
Your commentary should embellish the correctness and function of your code, not underline how insecure, unexpectedly behaving, mysterious, or potentially unreliable code it is. If there is remaining work to be done, that should be written as a To-Do point, containing sufficient information to guide you to a starting point for that work.
It takes practice to write good commentary (life-long practice, some would say), but unless your code is totally clear and unambiguous to the point of self-explaining to your grandmother, you should probably add some comments.
To round it off, here are a couple of disturbing - but funny - comments I have come across in production code:
Should I or should I not uncomment the first comment? Well, it has been inactive for 7 years, so I think I'll leave it as is, or remove it.
// ---- Better let sleeping dogs lie? Note the date. ----
try
if ValidParentForm(self) <> nil then
begin
ValidParentForm(Self).ActiveControl:=NIL;
//Turn this back on when I return from my holiday and have time to test it
//N.N. 20.07.2001
{if ValidParentForm(Self).ActiveControl=lFocusedControl then
//Control doesn't let go of focus, most likely because of some error message
exit; //Aborting the routine}
end;
if DataSet is TExtendedDataSet then
TExtendedDataSet(DataSet).Save
else
DataSet.Post;
finally
. . .
// ---- Unusual if/then/else construct ----
if (EditSomeProp.Text <> '')
and ((RequestTable[i].NUMREF+1) > StrToInt(EditSomeProp.Text)) then
// Watch out for Elsie here
else
begin
. . .
The second one is history, due to a rewrite.
Writing Readable Code - Paul's Snippet - Show your formatting!
Your anonymity is guaranteed (Unless you explicitly permit me to name you and/or link to a site or something).
Please note that there was a syntax problem in the original snippet, and I have indicated that below. I suggest that unless Paul instruct us otherwise, we take out the semi-colon after the end on that line.
try
if ConditionA then
Code(1)
else if ConditionB then
if ConditionC then begin
if ConditionD then
code(2);
code(3);
end; // I assume this semi-colon has to go
else if ConditionE then begin
code(4);
morecode('X');
if ConditionF then
code(5)
else if ConditionG then
code(6);
end else begin
code(7);
morecode('Y');
end;
finally
code(8);
end;
Wednesday, August 6, 2008
Writing Readable Code - Formatting and Comments
So - why bother to put any effort into the formatting anyways? It is not like the compiler care? I mean, apart from the obvious requirement that things should be reasonable readable and not just a jumble of code?
Layout matters. It is significantly easier to pick up code that is consistant in style than wading through spaghetti (or gnocchi) code where the indentation has not been considered much.
I have noticed that my personal style with regards to block formatting definitively is non-conformist. Whenever there is a do, if/then/else, with or without related begin/end, I will probably annoy the heck out of the conformists.
My formatting goals:
• I want clear indication of flow
• I want to separate conditions from actions
• I want room to comment
In the examples below, I will place comments in the individualist examples to give an indication of why I choose to wrap/indent in this way rather than the "global standard"
Let's start with the do in with and the for-loop (I do at times, albeit rarely, confess to the occasional use of with).
// Conformist
for i := 1 to 5 do Something;
for i := 1 to 5 do begin
Something;
SomethingMore;
end;
with SomeObject do begin
Something;
SomethingMore;
end;
// Individualist
for i := 1 to 5 // For selected range
do Something(i); // get stuff done
for i := 0 to (count - 1) // For every item
do begin
Something(i); // Step 1
SomethingMore(i); // Step 2
end;
with SomeObject // Focusing on this specific item,
do begin // I can add this comment for clarity
Something;
SomethingMore;
end;
The if/then/else statement have too many combinations to even get close to cover them all, so I am only going to do a handful.
// Conformist
if (SomeVariable = Condition) and (SomeOtherExpression) then
PerformSomeRoutine;
if (SomeVariable = Condition) and (SomeOtherExpression) then begin
Code;
MoreCode;
LotsOfCode;
end else OtherCode;
if Condition then DoFirstAlternative else DoSecondAlternative;
if FirstCondition then DoFirstAlternative
else if SecondCondition then DoSecondAlternative
else DoThirdAlternative;
// Individualist
if (SomeVariable = Condition) // I always put the condition alone
then PerformSomeRoutine;
if (SomeVariable = Condition) // Condition X found
and (SomeOtherExpression) // Requirement Y fulfulled
then begin // we can do our stuff
Code;
MoreCode;
LotsOfCode;
end
else OtherCode; // or we ended up with the alternative
if Condition
then DoFirstAlternative // The Condition compelled us to do this
else DoSecondAlternative; // Optionally, we explain the alternative
// Here I find myself doing many different approaches,
// depending on the complexity of the logic, and the number
// of alternatives, but with multiple nested if's, I tend to indent
if FirstCondition
then DoFirstAlternative // We are doing 1 because ...
else if SecondCondition
then DoSecondAlternative // We are doing 2 because ...
else DoThirdAlternative; // Otherwise, We are doing 3
// I might chose this approach if it is a long chain
if FirstCondition
then DoFirstAlternative // We are doing 1 because ...
else if SecondCondition
then DoSecondAlternative // We are doing 2 because ...
else DoThirdAlternative; // Otherwise, We are doing 3
C++/C# and Java people likes to rant about how ugly and verbose our begin / end's are, and I am sure I could rile myself up over their curly brace enclosures and some of the religion connected to those too - but I am not going to go there. However, there are a few things about our enclosures that may be worth thinking over.
Some like to dangle their end's at various indentation levels. I prefer to align it with the matching start of the block (do begin, then begin). Why? It makes it is easier to identify the code path in relation to the condition.
More enclosures doesn't always mean better readability. Here is an example from Indy9(IdTunnelCommon.pas). Kudzu, I love you - but I disagree with your code style in this file :)
(I know I am going to get flamed for this...^^)
procedure TReceiver.SetData(const Value: string);This little nugget very much bear the signs of being written for debugging, so I am not going to hold it to Kudzu for style (much). It includes a few nice examples of code that can be simplified, so it is all good.
var
CRC16: Word;
begin
Locker.Enter;
try
try
fsData := Value;
fiMsgLen := Length(fsData);
if fiMsgLen > 0 then begin
Move(fsData[1], (pBuffer + fiPrenosLen)^, fiMsgLen);
fiPrenosLen := fiPrenosLen + fiMsgLen;
if (fiPrenosLen >= HeaderLen) then begin
// copy the header
Move(pBuffer^, Header, HeaderLen);
TypeDetected := True;
// do we have enough data for the entire message
if Header.MsgLen <= fiPrenosLen then begin
MsgLen := Header.MsgLen - HeaderLen;
Move((pBuffer+HeaderLen)^, Msg^, MsgLen);
// Calculate the crc code
CRC16 := CRC16Calculator.HashValue(Msg^);
if CRC16 <> Header.CRC16 then begin
fCRCFailed := True;
end
else begin
fCRCFailed := False;
end;
fbNewMessage := True;
end
else begin
fbNewMessage := False;
end;
end
else begin
TypeDetected := False;
end;
end
else begin
fbNewMessage := False;
TypeDetected := False;
end;
except
raise;
end;
finally
Locker.Leave;
end;
end;
So what do I do? • I assume the except block was for debugging, but I'm taking it out. • I might be changing the behaviour by setting two default values up top, but at least there is no doubt about their default value. • But what is with the conditional assignments? Please! BooleanVariable := Expression; !! Don't go "then true else false" on me! • Reworked the comments.
Personally, I think it is more readable like this.
procedure TReceiver.SetData(const Value: string);Now, where is that flameproof tin foil dress...
var
CRC16: Word;
begin
fbNewMessage := False;
TypeDetected := False;
Locker.Enter;
try
fsData := Value;
fiMsgLen := Length(fsData);
if fiMsgLen > 0
then begin // Data found, Check for Type
Move(fsData[1], (pBuffer + fiPrenosLen)^, fiMsgLen);
fiPrenosLen := fiPrenosLen + fiMsgLen;
TypeDetected := (fiPrenosLen >= HeaderLen);
if TypeDetected
then begin // We have a type, check header for content
Move(pBuffer^, Header, HeaderLen);
fbNewMessage := Header.MsgLen <= fiPrenosLen;
if fbNewMessage
then begin // we have enough data for the entire message
MsgLen := Header.MsgLen - HeaderLen;
Move((pBuffer+HeaderLen)^, Msg^, MsgLen);
CRC16 := CRC16Calculator.HashValue(Msg^); // Calculate the crc code
fCRCFailed := CRC16 <> Header.CRC16; // and check if it was correct
end;
end;
end;
finally
Locker.Leave;
end;
end;
Next: I'll be rambling a little more on effective comments, naming and structure.
Edit - added after Paul's Snippet Rewritten: Delphifreak likes to exit early. He also likes to initialize up top, and I totally agree - that is a good practice. Exit works well in this example where all the conditions are dependant on the previous one. IMO, things get a lot more hairy if there are multiple conditions with alternatives (if/then/else if). For this example, Exits are not bad.
procedure TReceiver.SetData(const Value: string);
var
CRC16: Word;
begin
fbNewMessage := False;
TypeDetected := False;
Locker.Enter;
try
fsData := Value;
fiMsgLen := Length(fsData);
if fiMsgLen = 0
then Exit;
// Data found, Check for Type
Move(fsData[1], (pBuffer + fiPrenosLen)^, fiMsgLen);
fiPrenosLen := fiPrenosLen + fiMsgLen;
TypeDetected := (fiPrenosLen >= HeaderLen);
if not TypeDetected
then Exit;
// We have a type, check header for content
Move(pBuffer^, Header, HeaderLen);
fbNewMessage := Header.MsgLen <= fiPrenosLen;
if not fbNewMessage
then Exit;
// we have enough data for the entire message
MsgLen := Header.MsgLen - HeaderLen;
Move((pBuffer+HeaderLen)^, Msg^, MsgLen);
CRC16 := CRC16Calculator.HashValue(Msg^); // Calculate the crc code
fCRCFailed := CRC16 <> Header.CRC16; // and check if it was correct
finally
Locker.Leave;
end;
end;
Tuesday, August 5, 2008
Why Free Software has poor usability, and how to improve it
In 15 points, he puts the finger on some potential issues with free software. Since I am in the process of trying to make some, I found many of the points to be familiar, and sometimes even too close for comfort. His essay contain some important reminders and well-developed ideas for improving the process of designing and developing software intended for public consumption.
Here is the list of points:
• Few good designers
• Design suggestions often aren’t invited or welcomed
• Usability is hard to measure
• Coding before design
• Too many cooks
• Chasing tail-lights
• Scratching their own itch
• Leaving little things broken
• Placating people with options
• Fifteen pixels of fame
• Design is high-bandwidth, the Net is low-bandwidth
• Release early, release often, get stuck
• Mediocrity through modularity
• Gated development communities
Permalink to Matthew Paul Thomas's article: http://mpt.net.nz/archive/2008/08/01/free-software-usability
Spotted at SlashDot.
Sunday, August 3, 2008
Understanding RESTful services
The term was coined by Roy Fielding in his Ph.D. dissertation in 2000, so it is not really a new thing. Some people like to wave the term around like a new mysterious tool that only the chosen ones can understand and wield. Some authors like to dance around the topic without getting down to the elevator speech description and go into great and elaborate examples on how "RPC is too complex in this or that scenario, and this is why you should use REST instead". Cut to the chase already!
One book that do actually get to the point, is RESTful Web Services by Leonard Richardson & Sam Ruby (O'Reilly, ISBN 0-596-23926-0). Easy read, good examples, sensible commentary.
So... what is REST ?
Key: The URI is our permanent and unique key to the content
Content: Can be anything, really, as long as it can be sent across the transport
Transport: HTTP (Get, Put, Update, Delete)
Transaction: Always one complete operation per REST object
Ok, so that might be a tad oversimplified, but it actually isn't much more complicated than that. In a way, you can compare it to a file system, and depending on how you build your content, it can be cached like a file system, in your ISPs cache, the company firewall cache, and your desktop browser cache.
Was that too easy? Well, there are some areas that are not quite as well defined, such as content description, content discovery, and access control - but there are more than one way to Rome here. Eventually, some sort of best practice will probably emerge, but right now it is sort of every RESTful service for itself.
IMO, for a developer - the true challenge with REST is deciding on how to construct your data URIs and how to partition up your data so that they are easily and logically accessible. You have to consider such stuff as categories, filtering, time range, wildcards, etc. If you intend to cache the data, you also need to figure out versioning so that you don't get stuck with stale data. On the coding side - the real action happens behind the http transport on your server, where you have to do your stuff to pack and deliver or unpack and store your data.
It turns out that REST is not even a bit mysterious. Some people just like to pretend that it is. You don't need huge commercial complex frameworks to write a REST service. Come to think of it, Delphi is a great tool to write REST servers in. It has all you need: good parsing tools, database integration, http components, etc. </soapbox>
Saturday, August 2, 2008
Reusable Grid View - part 4 - Code Complete
It contains the reusable grid view controller, a small color utility unit and three very simple demos.
The code is currently only tested under Delphi 2007, and the color utility unit implements a record with methods, so I guess that breaks older versions, but it is easy to change. I'll probably add some conditional code for compatibility later.
Details about versions downloading can always be found at http://fdclib.fosdal.com. You can download a .zip file or grab it with SVN from the repository at SourceForge. I haven't created a download package on SourceForge yet, but that is coming as well.
The Grid Demos
There are three very simple demos included in the demo/DemoFDCLib project. I'd like to add some more later, but I think they demonstrate the basic functionality for now.
• DemoFrameGridViewController contains the interactive bits (ie the TStringGrid, etc.)
• DemoNumbersViewClass show a series of numbers and their value squared, and also show how to use a custom static column color.
• DemoColorsViewClass shows how to implement a procedural color and contains $FFFFFF rows :) Loading time is nonexistant.
• DemoDirectoryClass is a small utility class that populate a TStringList from a directory path with wildcards. DemoDirectoryViewClass implements the grid controller for showing content from that extended string list. The default directory in the demo is "%temp%\*", but feel free to experiment with changing it in the edit box.
Anonymous Methods - When to use them?
I believe there are some areas where anon.methods will have a significant impact. Not dramatic or revolutionary - but significant.
That said, we do really need something solid which demonstrate the advantages of anon.methods beyond the trivial one-liner demo methods (which easily can be done in the "old ways"). Until we see some, here is some speculation and conjecture from my side...
1. Lambda expressions and Generics
I assume that for generic classes, the compiler generates something to the effect of anon.methods behind the scene. I also assume that anon.methods are one of the building blocks in type inference.
2. Isolation Glue
When you write classes that interact in the traditional OOP way, you often have to derive new classes from the base classes and implement a set of features (read: method override) where you apply your knowledge of the two new classes to create new interaction rules. Very often, such override methods are just a handful of lines. Yet you have to add yet another two classes to achieve it. In concept, this is a similar problem domain to generics - but here we could be talking about trying to make two specific classes (such as a visual and a non-visual) work together without having to reimplement all the plumbing in great explicitness.
Anon.methods can simplify this by allowing one of the classes to implement all the glue using anon.methods instead of having to implement overridden virtual methods in both classes.
3. Threads
Threads today are a fairly elaborate construction project. Anon.methods may enable us to create something similar to fibers. Where the old style single kernel fibers had to deal with manual scheduling on one CPU, today's multi-core aware fiber management code can delegate the "packaged" anon.methods and data (or fibers if you like) to multiple kernels for actual concurrency.
In what way will this differ from threads? Threads are elaborate to design, requiring yet another class descendant to implement the Execute method. You have to manually feed them their data/scope, and manually retrieve any generated content, and manually implement a "tie it all together" sync.point (ie where you are waiting for all threads to complete so that you can continue). They are expensive to set up, kick off and there is a lot of housekeeping involved.
From the top of my head, here are some forms of processing can be compartmentalized using anon.methods without the overhead of multiple thread class implementations: Sorting (key generation/comparison), matrix math / SSD type processing like compression/decompression, and other types of algoritmic code. In theory, any sequential processing that don't have backwards or forwards dependencies in the dataset, would be trivial to parallelize.
The result should be less obstacles to writing code that actually can utilize all your hardware.
• Is it possible to do this with the existing TThread? Yes it is.
• Can anon.methods reduce the complexity of doing it? Most likely, yes.
• Is it is still possible to do horrific mistakes? Yes, but they are the same old mistakes as for regular threads (race, starve, deadlock, data you can't/shouldn't touch, etc), and since you are now defining your thread/fiber in the scope of it's deployer - it may be that the compiler can make more intelligent judgement about the validity of your thread/fiber.
Will anon.methods lead to spaghetti?
I don't think so. Generally speaking, they may make some things clearer as more of the logic can be packed into one class, instead of being spread over multiple related classes. All "generic" code (pardon the pun) can be kept simple and ..ehm.. generic, and you don't have to build a huge inheritance tree where methods are virtualized up the wazzoo.
I think a possible factor in explaining why it is hard to come up with examples that are short, detailed and easily understood, is that we will benefit the most from anonymous methods in code that is anything but trivial.
P.S. In Bart Roozendaal's blog about anon.methods, Thomas Mueller mentions that the good old Turbo Pascal TCollection ForEach and FirstThat will be possible again. That's not a bad example either. I actually missed those a lot when I couldn't use them anymore.
Edit: Make sure to read Jarle Stabell's article on Lambda functions!
Edit 2:
• An older post from Barry Kelly on how how closures presents a challenge in Delphi for Win32. I guess we know by now that they landed on refcounting.
• The "Pascal gets Closures before Java" Reddit thread.
Edit 3:
• Wayne Niddery on Understanding Anonymous Methods
• Jolyon Smith on Anonymous Methods - When should they be used?
• Andreano Lanusse - Tiburon - Anonymous Methods
Edit 4: • Joel Spolsky - Can your programming language do this?
Thursday, July 31, 2008
Anonymous methods - Variable Scope?
What variables that are in scope at the point of definition will be allowed used within the anonymous method, and which references should be avoided?
Can the method be defined without being assigned to a local variable, ie directly as a parameter to a call?
With reference to the articles on the TGridViewController, would it be possible to convert this (class method pointer)...
to this? I.e. in-line declared anonymous methods
procedure TViewDirectory.DefineAttributes;
begin
CreateTextColumn('Name', GetFileName, 300);
CreateTimeColumn('Date', GetFileDate, 100, taCenter);
CreateIntegerColumn('Size', GetFileSize, 100, taRightJustify);
end;
// Note that Dir is a protected class property of TViewDirectory
function TViewDirectory.GetFileDate(const Row:Integer):TDateTime;
begin
Result := FileDateToDateTime(Dir.Entry[Row].Time);
end;
function TViewDirectory.GetFileName(const Row:Integer):String;
begin
Result := Dir.Entry[Row].Name;
end;
function TViewDirectory.GetFileSize(const Row:Integer):Integer;
begin
Result := Dir.Entry[Row].Size;
end;
Apart from in-parameter definition (and any related syntactical mistakes I might have made), the problem here would be the reference to Dir. Since Dir is a class property (or private variable), it seems possible that this would be quite safe.
procedure TViewDirectory.DefineAttributes;
begin
CreateTextColumn(function (const row:integer):String;
begin
Result := Dir.Entry[row].Name;
end, 'Name', 300);
CreateTimeColumn(function (const row:integer):TDateTime;
begin
Result := FileDateToDateTime(Dir.Entry[row].Time);
end, 'Time', 100, taCenter);
CreateIntegerColumn(function (const row:integer):Integer
begin
Result := Dir.Entry[row].Size;
end, 'Size', 100, taRightJustify);
end;
What about local variables?
Is this legal?
procedure DefineAttributes;I am really looking forward to learn more on this!
var
Reg : TRegistry;
Key, Value : TStringList;
begin
Reg := TRegistry.Create;
key := TStringList.Create;
Value := TStringList.Create;
try
Reg.OpenKeyReadOnly('\Software\Somewhere');
Reg.GetKeyNames(Key);
Reg.GetValueNames(Value);
// we have ensured that GetRowCount returns the correct count
CreateTextColumn( function (const row:integer):string;
begin
Result := Key[row];
end, 'Key');
CreateTextColumn( function (const row:integer):string;
begin
Result := Value[Row];
end, 'Value');
finally
FreeAndNil(Value);
FreeAndNil(Key);
FreeAndNil(Reg);
end;
end;
Monday, July 28, 2008
Reusable Grid View - part 3
You will be assimilated
This is pretty self explanatory, but what we basically do is hook the draw routine and set up the appropriate number of rows and columns, and their respective default widths.
procedure TGridViewController.Refresh;OnDrawCell
var
ix : Integer;
begin
if not Assigned(Grid)
then Exit;
if not IsEmpty
then begin // Set Visitor's drawing handler, row and column count
Grid.Enabled := True;
Grid.OnDrawCell := DrawCell;
Grid.RowCount := RowCount + FixedRows;
Grid.ColCount := Count;
for ix := 0 to Count - 1 // Set column widths
do begin
Grid.ColWidths[ix] := (Objects[ix] as TGridViewColumn).Width;
end;
end
else begin // disable drawing handler, set rows/cols to 1 and fill in 1 "blank"
Grid.Enabled := False;
Grid.OnDrawCell := nil;
Grid.RowCount := 1;
Grid.ColCount := 1;
Grid.ColWidths[0] := 0;
Grid.Cells[0,0] := defaultEmptyCell;
end;
if Grid.RowCount > 1
then begin // Reset header and Row positions in case empty grid overrode them.
Grid.FixedRows := FixedRows;
Grid.Row := FixedRows;
end;
Grid.Invalidate; // ensure that the grid is refreshed
end;
So this is where all the action is... err... actually, not much happens here. Instead we delegate the actual rendering to the column instance.
procedure TGridViewController.DrawCell(Sender: TObject; ACol, ARow: Integer;The column instance then again divide the draw into two sections. The outer DrawCell does basic housekeeping of canvas color settings and a little color trickery (a feature that snuck in while I was having fun - my bad).
Rect: TRect; State: TGridDrawState);
begin
(Objects[aCol] as TGridViewColumn).DrawCell(Grid.Canvas, aRow, Rect, State);
end;
procedure TGridViewColumn.DrawCell(Canvas: TCanvas; aRow: Integer; Rect: TRect;The actual content rendering happens in DrawCellInner. Note that we do an adjustment related to grid layout here. If you look at the line where DrawCellInner is called, we subtract FixedRows from aRow, ensuring that our data is zero offset based.
State: TGridDrawState);
var
FG, ofg,
BG, obg : TColor;
begin
with Canvas
do begin
ofg:=Font.Color;
obg:=Brush.Color;
if (State = []) // Not focused/selected/etc
then begin
FG := ofg;
BG := obg;
if aRow > 0 // Not a title line
then begin
ForeColor(aRow, FG);
BackColor(aRow, BG);
end;
if ofg <> FG then Font.Color:=FG;
if obg <> BG then Brush.Color:=BG;
end;
DrawCellInner(Canvas, aRow - Controller.FixedRows, Rect, State); // The business happens here
if (State = [])
then begin
if ofg <> FG then Font.Color:=ofg;
if obg <> BG then Brush.Color:=obg;
end;
end;
end;
procedure TGridViewColumn.DrawCellInner(Canvas: TCanvas; aRow: Integer;In the inner draw routine we first figure out if we are drawing the title row or a data row. If it is a data row, we call the TGridViewColumn.FormattedText method.
Rect: TRect; State: TGridDrawState);
var
CellText : String;
w : Integer; // String width
txtAdj : Integer; // old adjust mode
begin
if aRow < 0
then CellText := Title
else CellText := FormattedText(aRow);
with Canvas
do begin
case FAlign of
taCenter : begin
w:=TextWidth(CellText);
if (w>(Rect.Right-Rect.Left))
then w := 2
else w := Round(((Rect.Right - Rect.Left) - w) / 2);
TextRect(Rect, Rect.Left + w,Rect.Top + 1, CellText);
end;
taRightJustify : begin
txtAdj:=SetTextAlign(Handle, ta_Top or ta_Right);
TextRect(Rect, Rect.Right - 3,Rect.Top + 1, CellText);
SetTextAlign(Handle, txtAdj);
end;
else // taLeftJustify
TextRect(Rect, Rect.Left+2,Rect.Top+1, CellText);
end;
end;
end;
function TGridViewTextColumn.FormattedText(aRow: Integer): String;Does GetTextMethod look familiar? It is the procedure variable which holds the method we implement in our descendant viewcontroller class and pass on when we set up the column. That is the method which actually retrieve the data from the underlying data structure as described in part 2 (GetNumber, GetNumberSquared).
begin
Result := GetTextMethod(aRow);
end;
The rest of DrawCellInner measures the retrieved text and ensure it is adjusted as desired and rendered within the cell region.
Magic Colors
Back to ForeColor and BackColor in the outer draw. This is the closest we come to bells and whistles. To make the grid look more sophisticated, I added support for overriding the colors as well as a very simple default rowshading algorithm.
procedure TGridViewColumn.ForeColor(aRow: Integer; var Color: TColor);Both these methods start in the same way. The default color have been retrieved from the grid itself in the outer draw. First we check if a color method have been plugged in, if not - we check if a column color override has been set.
begin
if Assigned(FGetForeColorMethod)
then GetForeColorMethod(aRow, Color)
else if DefaultForeColor <> clNone
then Color := DefaultForeColor;
end;
procedure TGridViewColumn.BackColor(aRow: Integer; var Color: TColor);
begin
if Assigned(FGetBackColorMethod)
then GetBackColorMethod(aRow, Color)
else if DefaultBackColor <> clNone
then Color := DefaultBackColor;
if Assigned(FGetShadeColorMethod)
then GetShadeColorMethod(aRow, Color);
end;
For the background color routine, there is another feature - a pluggable shading method. The default row shader looks like this...
procedure TGridViewController.DefaultRowShader(const aRow:Integer; var Color:TColor);Too much flexibility? Well, maybe I broke the KISS rule, but no rule without exceptions :) Now I can have static or procedural color adjustments per row as well as per column.
begin
if ShadeRows and (not Odd(aRow))
then begin
Color := Graphics.ColorToRGB(Color); // Translate theme/system colors to RGB
if ((Color and $FFFFFF) > $888888) // Lazy man's luminosity adjustment
then Color := Color - $080808
else Color := Color + $080808;
end;
end;
Next: Reusable Grid goes code complete and come with examples.
Stay tuned.
Writing reusable code - part 2
Note that I in no way claim this to be the one and only true way to software development nirvana, but just some practices that work for me. Your own opinions and experiences on the topic will be greatly appreciated, so feel free to comment. |
Keep It Simple, Stupid
Reusable code should be kept low-feature. It can be a challenge to avoid feature creep and dependency bloat, so make an effort to follow the KISS rule. The more sophisticated stuff you add, the more you will need to "fix" and not to forget: Test! - when inheriting from the class.
Design for extendability
When you design a reusable class, you really have to define the scope of what you want to achieve. Decide on the basic feature set, then evaluate the possible offspring to this class and conceivable additional features, but to adhere to the KISS rule - you should not add the features until you need them. Instead, spend a little time on evaluating the impact of the features and put in any necessary hooks and leave enough comments to allow you to rediscover why you put them in in the first place.
Later, if you absolutely have to add a feature and cannot add it in an inherited class - make sure you don't break existing behavior in class instancing. A common mistake is to change a default property value in the base class constructor, which some derived class assumed had a specific state or value.
...and on the point of assumptions...
Never Assume, Always Assert
I bet you never heard that one before! :) Use Asserts to check your assumptions. It will save you from a number of embarrassing moments of broken code. The purists probably want this done in unit testing, but it can help add clarity to your code if it is done in the actual library code. Think of it as design by contract.
procedure TSimpleClass.ActOn(aParameter:TSomeClass; aValue:Integer);Using asserts like these will help you catch runtime problems faster. They also will remind you of what conditioning you need to do before you call the method, and what assumptions you can make within the routine itself.
begin
// Ensure aParameter is assigned and of correct type
Assert(Assigned(aParameter) and (aParameter is TSomeClass));
// and that aValue is in legal range
Assert(aValue >= 0);
...
Avoid Dependencies
When you start pulling in code from other libraries or classes, you should consider the level of dependency that you introduce. A simple extra nice-to-have attribute may come with a payload of extra code that hardly ever will be called. If possible, put the extra attribute in a separate descendant class in a separate unit. The less code you have to compile in, the better.
Embrace Polymorphism
When you create classes that are intended as foundation for descendant classes, you should consider how to best support polymorphic instancing.
For example - if you are writing a container class that will hold a polymorph collection of elements - you may want to have a virtual class function that return the default class type used to instance new elements in the container, instead of just instancing the base element class. Doing so will allow descendant classes make reliable assumptions about the safety of typecasting within the extended methods in the descendant container class.
unit BasicClass;"But..." - you will say; "you can just override methods in the descendant class for the same effect?". Yes, you can and it works well when you add new functionality, but what about functionality that already has been implemented in class higher in your inheritance tree?
interface
type
TElementClass = class of TBaseElement;
TBaseElement = class(TObject)
end;
TBaseContainer = class(TObject)
class function ElementClass:TElementClass; virtual;
function ValidElement(anElement:TBaseElement):Boolean;
end;
implementation
{ TBaseContainer }
class function TBaseContainer.ElementClass: TElementClass;
begin
Result := TBaseElement;
end;
function TBaseContainer.ValidElement(anElement: TBaseElement): Boolean;
begin
Result := anElement is ElementClass;
end;
end.
unit AdvancedClass;The base class won't have an understanding of it's descendants, so if you want it to be able to work with content from descendant classes, you would either have to override/redo the functionality or - do as above - allow the base class to work with their own child classes as well.
interface
uses
BasicClass;
type
TAdvancedElement = class (TBaseElement)
end;
TAdvancedContainer = class(TBaseContainer)
class function ElementClass:TElementClass; override;
end;
implementation
{ TAdvancedContainer }
class function TAdvancedContainer.ElementClass: TElementClass;
begin
Result := TAdvancedElement;
end;
end.
program ExtendableClasses;
{$APPTYPE CONSOLE}
uses
SysUtils,
BasicClass in 'BasicClass.pas',
AdvancedClass in 'AdvancedClass.pas';
procedure Main;
var
Element1, Element2 : TBaseElement;
Container1, Container2 : TBaseContainer;
begin
Container1 := TBaseContainer.Create;
Container2 := TAdvancedContainer.Create;
Element1 := Container1.ElementClass.Create;
Element2 := Container2.ElementClass.Create;
Writeln('E1: ', Element1.ClassName);
Writeln('E2: ', Element2.ClassName);
Writeln('E1 valid in C1: ', Container1.ValidElement(Element1));
Writeln('E2 valid in C1: ', Container1.ValidElement(Element2));
Writeln('E1 valid in C2: ', Container2.ValidElement(Element1));
Writeln('E2 valid in C2: ', Container2.ValidElement(Element2));
end;
begin
try
try
Main;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
finally
Write('Press Enter: ');
Readln;
end;
end.
This particularly goes for dialogs and forms that use a polymorph class - make a virtual class function to enable overriding the class instancing. Avoid the rigidly defined TSomeInheritableClass.Create constructs and call the virtual class type function instead.
Note on constructors When I finally "got" OOP as opposed to structured programming, it was hard to drop the structured way of thinking when it came to instancing. Most of my classes would only have a constructor that took a ton of parameters to save a few lines at the point of instancing that class. While this might work well for monomorphic classes, it really will not be good for a class hierarchy, and it took me some time to get to the habit of having a parameterless Create. Today, I almost wish for a compiler hint if I should happen to write constructors that require parameters. |
Hide detail with Layers
Think in APIs and layers and try to propagate only content between the layers, and not details that are specific to connectivity, storage, etc. A good layered design will allow you to rip out and replace parts of the architecture without massive rewrites.
When you design your APIs or layers, you should consider leaving space or hooks for piggybacking in more content in yet-to-be defined formats where appropriate.
Isolate Data from the GUI
The less your GUI know about your data, the better. Vise versa, the less your data knows about the GUI, the better. Use a mediating class that does the job of knowing both and how to tie them together. This will ease the job of making the data structures as well as the GUI reusable.
It can be particularly useful to tie your business logic to your data and avoid implementing biz.logic in the GUI presentation. Your data will be easier to move to a different presentation form when the rules are not hard-coded in some draw routine. You don't want to have duplication of biz.logic code between the display routines, the report routines, the export routines, etc.
-
Thats it for now. More on the topic of Layers and GUI abstraction to follow.
Also, part 3 of the reusable grid.
Wednesday, July 23, 2008
Reusable Grid View - part 2
Our basic TGridViewController will hold a list of TGridViewColumns. I could make a simple array, or I could use TStringList. TStringList is probably one of the best and one of the worst classes in Delphi. So easy to use and so easy to abuse. In this case, I will only need to manage a handful of columns, instead of thousands of lines of text - so it will be ok.
On the abuse side: before you start filling TStringList - consider adjusting the capacity. Resizing the list is EXPENSIVE. Also - fill first, sort later.
Ok, lets take a look at the initial skeleton of the controller class:
TGridViewController = class (TStringList)
private
FGrid : TStringGrid;
function GetIsEmpty:Boolean;
function GetCurrentSelectionIndex: Integer;
protected
procedure DefineAttributes; virtual; abstract;
function GetRowCount: Integer; virtual; abstract;
procedure SetGrid(const Value: TStringGrid); virtual;
procedure DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
function MakeColumn(ColType : TGridViewColumnType; aTitle:String; aWidth:Integer; anAlign:TAlignment):TGridViewColumn;
public
constructor Create; virtual;
destructor Destroy; override;
procedure Refresh;
function CreateTextColumn(aTitle:String; aGetText:TGetTextMethod; aWidth:Integer = 64; anAlign:TAlignment = taLeftJustify):TGridViewTextColumn;
property CurrentSelectionIndex:Integer read GetCurrentSelectionIndex;
property Grid: TStringGrid read FGrid write SetGrid;
property IsEmpty: Boolean read GetIsEmpty;
property RowCount:Integer read GetRowCount;
end;
I won't go into the details on the TGridViewColumn yet, but note the two abstract methods in the controller: DefineAttributes and GetRowCount. To make a working grid, these are what we need to override.
The Grid property identifies the grid that we will control and DrawCell is the method we plug into the grid onDrawCell event. MakeColumn is called by the CreateTextColumn with the appropriate TGridColumnView descendant - hiding the interaction with our parent string list. I guess you already have figured out that IsEmpty simply check the number of rows.
A simple Example
Lets consider the simplest possible example: a grid showing rows with the numbers 1 to 5, and their values squared. Note that the unit is pretty ignorant about what a grid is.
unit DemoNumbersClass;
interface
uses
Classes, SysUtils, FDCGridViewController;
type
TViewNumbers = class(TGridViewController)
private
function GetNumber(const Row: Integer): String;
function GetNumberSquared(const Row: Integer): String;
protected
procedure DefineAttributes; override;
function GetRowCount: Integer; override;
end;
We define two columns, and refer each column to a data retrival method.
implementation
{ TViewNumbers }
procedure TViewNumbers.DefineAttributes;
begin
CreateTextColumn('Number', GetNumber);
CreateTextColumn('Squared', GetNumberSquared, 64, taRightJustify);
end;
function TViewNumbers.GetNumber(const Row: Integer): String;
begin
Result := IntToStr(Row);
end;
function TViewNumbers.GetNumberSquared(const Row: Integer): String;
begin
Result := IntToStr(SQR(Row));
end;
function TViewNumbers.GetRowCount: Integer;
begin
Result := 5;
end;
end.
Then we create a Form, drop a string grid on it, tweak some row size settings etc., and add the controller.
unit DemoFDCGridViewController;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms,
StdCtrls, Grids, FDCGridViewController, DemoNumbersClass;
type
TGridViewForm = class(TForm)
StringGrid1: TStringGrid;
procedure FormCreate(Sender: TObject);
private
Numbers : TViewNumbers;
public
{ Public declarations }
end;
var
GridViewForm: TGridViewForm;
implementation
{$R *.dfm}
{ TGridViewForm }
procedure TGridViewForm.FormCreate(Sender: TObject);
begin
// Create our controller
Numbers := TViewNumbers.Create;
// and introduce the controller to the host grid
Numbers.Grid := StringGrid1;
end;
end.
And the result:
As you can see, there are a few tweaks that I haven't described yet. Also, I still haven't revealed the simple steps done to invade our host grid.
Stay tuned :)
Source Code highlighting in Blogger
When the scripts are in the template, all you need to put in Blogger is:
<pre name="code" class="delphi">
program Demo;
begin
// Say hi
Writeln('Hello World');
end.
</pre>
... and it shows up as:
program Demo;
begin
// Say hi
Writeln('Hello World');
end.
Tuesday, July 22, 2008
Vacation Pattern
2. Travel to far off region
3. Take pictures
4. Buy T-Shirt
5. Return home
6. Upload pictures
7. Write blog entry and refer to pictures
8. Apologize for off-topic blog post
9. Promise new on-topic article real-soon-now
Wednesday, July 9, 2008
Writing Reusable Code
But, as the saying goes - publish or perish - so here I go again but this time in a hopefully better protected environment.
This is the first article in what I hope will be a series on creating lightweight reusable code, and although I have no ambitions about making this a widespread toolkit, I hope it may be of use to someone.
It certainly is of use to me :)
Reusable Grid View - part 1
Needful Things
Did you ever write your data to a logfile to check them out? Maybe you wrote them to a string list and showed them in a Memo? Maybe you even stuffed them into a TStringGrid and found out the hard way how expensive that can be if you have a lot of data and how poorly the numeric data display as a string. You often end up having to tweak and re-tweak the formatting to make the dump readable.
After having to implent similar gridviews in three different tasks back to back and seeing more on the horizon, I decided that I wanted to make it easier to quickly put together a gridview for a specific set of data.
There are a lot of nice custom grid controls out there, but they all come at a price - either in the form of purchase, or in the form of having to invest time in writing glue to get your data in there, or even with cost of pure payload. Some grids are so function rich that they almost are a system in their own right.
Totally ignoring the not-invented-here syndrome warning lights, I decided to make my own little quick grid viewing kit.
Design goals
- Make it ignorant to the underlying data structure
- Avoid having to adapt your data structure to this specific viewer
- Avoid duplicating data
- Make it easy to configure
- Make it easy to extend and modify
Design tradeoffs
- The underlying data must have a known number of rows
- Each column will be have one type of content
- Make it reasonably stupid
Bells: No
Whistles: No
Nice to haves: Well, maybe a few :)
Isolating the data and the viewer
In a way, the top three goals are in conflict. How can you access content if you don't know the format of the content? If you don't know the format, don't you have to convert it somehow, and don't you have to store that conversion? If you don't adapt the data to the viewer, how can you make a generic viewer?
Those that work with databases and web probably already know the answer: Base the solution on the Model View Controller pattern.
We need to find an effective way to encapsulate the data and present that encapsulation to the grid control. Let's briefly look at the options for encapsulating the data here.
- Inheritance - Not good - I don't want to force our data to be based on a type specific to my viewer
- Aggregation - I can make a class that knows both the dataset and the viewer
Aggregation allows me to write a base class that incorporate all the knowledge about the grid side of the problem, leaving only the need to implement a reasonably thin data access layer.
Since I don't want to duplicate content into the grid, I need to find a good way to retrieve what I need on demand and to massage it into the format I need to display it on the fly. The solution is to use something along the lines of a Visitor pattern. Since grid views typically have static columns - ie all fields in a column is of the same type - I will make the visitor look at the data from a column perspective, identifying which row it is visiting. This will be the column visitor (aka TGridViewColumn). I will create different type of grid view columns for different type of data. String, boolean, integer, float, date, and even an image one - which basically is just the integer visitor with a custom draw.
The Grid Controller
To avoid having to recreate the grid from scratch, I am going to attach myself to a TStringGrid. I am not going to inherit from it, just wrap it and inject myself where it counts. Why no inheritance? Well - I might want to move this code to a more capable grid at a later point in time. Also, by wrapping it - I can attach myself to a standard grid that is dropped in at design time without having to make a new component. This will be my grid controller (aka TGridViewController).
Custom Drawing
My key point for doing all this is to be able to display the data as best as I can. Hence, I will replace the TStringGrid cell drawing routine with my own. Firstly, I need a new string drawing routine. I'm going to add center and right justify, so that it will be easy to convert a number to a string, and then draw the string right justified. Fortunately, TStringGrid.OnDrawCell allows me to do inject my custom draw without severe tampering with TStringGrid.
I wasn't going to put in a lot of bells and whistles in my reusable grid view, but one thing that I thought would be useful was the abilty to control row color and column color. If I am viewing some sort of log - it would be nice if I could highlight a row with a problem, or a row matching some sort of highlight criteria. Again - I want to give the controller the same ability to tie together the underlying data and the color changes, so I will use the same visitor model for retrieving the customized color.
I am splitting my custom draw routine between the grid controller and the column visitor. This allow me to match the drawing customisation to the data access and conversion, both from a row as well as a column perspective. The column draw routine is actually split in a generic housekeeping outer draw routine, and a specific detail inner draw routine.
Property Getter
Initially I thought I would write descendant classes for each GridViewColumn per implementation. I.e. for each new view, I would write a class per column, inheriting the class from the appropriate column type (string, mumber etc). But then I realized that would mean quite a few extra lines of code to set up a grid.
So- how about using a pluggable visitor routine instead? That way I could actually implement the column data type column only once - and only add a single visitor routine in the custom GridViewController to retrive the actual value that is to be translated and drawn. The extra call overhead isn't really signficant since we are not typically talking several thousands of calls, but usually just a few hundred. Hence the GridViewColumn now has it's own property which hold the function used to retrieve the cell value from the GridViewController.
type
TGetTextMethod = function(const row:Integer):String of object;
TGetDoubleMethod = function(const row:Integer):Double of object;
That's all for the first part. More to come.
Proper database configuration is essential
Tuesday, July 1, 2008
Asking the right questions: What if...
To me, software development is a continuous search for answers...
- What is the problem?
- Is that really the problem?
- What is the preferred solution?
- How should it work?
- How must it work?
- How do we make that happen?
- How should we break down the tasks?
- How long will it take?
- What will it cost?
- Is it worth it?
- Is there a plan B?
When you finally get down to the core programming task, again it boils down to asking the same question over and over:
What if ... ?
There are no stupid questions.
Programming Related Blogs
-
-
-
-
The Great Filter Comes For Us All2 days ago
-
-
IceFest in Pennsylvania4 years ago
-
-
Aaron Swartz11 years ago
-
Setup IIS for Episerver CMS8 years ago
-
-
Never Defragment an SSD13 years ago
-
Welcome to BlogEngine.NET6 years ago
-
-
Somasegar's blog - Site Home - MSDN Blogs8 years ago
-
-
-
The 2020 Developer Survey is now open!4 years ago
-
CodeSOD: On VVVacation14 hours ago
-