Code Elegance
Yet another developer's musings on software design.
Monday, September 26, 2011
MSBuild Properties in Delphi
Tuesday, August 30, 2011
The "With" Statement
with
statement's intended purpose is to reduce repetitive typing. Some developers think its the greatest thing since sliced bread. Others are certain it was created by Satan. Let's examine a few languages that support it.Pascal
Thewith
keyword was present in the original Pascal language, developed in 1968-70 and published in Acta Informatica, Vol. 1 in 1971. I've found no description of a with
statement in Wirth's prior language projects or in any of the languages which influenced Pascal's design. Though admittedly, my research into its origins is limited. I'll assume for the moment that it was an original idea.
The
with
statement was originally intended to only be used with record variables. Here's a modification of a sample record from the original language description.
type
Date = record
day: 1..31;
month: 1..12;
year: 0..9999;
end;
var
today: Date;
The two following blocks are equivalent:
begin
with today do
begin
day := 25;
month := 12;
year := 2011;
end;
end;
begin
today.day := 25;
today.month := 12;
today.year := 2011;
end;
Object oriented descendants of Pascal added support for classes and interfaces. The main criticism of
with
is that it introduces the possibility of ambiguous references:var
day: integer;
today: Date;
begin
with today do
begin
day := 25;
month := 12;
year := 2011;
end;
end;
Which day
is being assigned to? Intuition may lead you to think that it is the one within the scope of the date
record and you'd be right, at least in this case. However, when this block is in the body of an object method, which references multiple other objects in the same with
statement it isn't so clear.
Where things really fall apart is when you are referencing a local variable from within the body of a
with
statement and a field or property with the same name is added to the definition of the object or record referenced by the with
statement. Suddenly code that was working flawlessly fails and all you did was add a field to a class definition. Heck, you haven't even referenced the new field in your code yet... or so you think.So that's Pascal's implementation. I would recommend you avoid using it whenever possible and use great care if you find it necessary to use it. What other languages support the
with
statement? Let's take a look.VisualBasic
With today
.day = 25
.month = 12
.year = 2011
End With
Here we see dot notation preceding the fields. This makes it apparent which identifiers are fields and which are not. This syntax was carried forward into VB.NET as well. There is still the possibility of ambiguity if you write a nested
with
statement. According to the documentation the compiler would only see the fields of the object referenced by the inner with
statement until the statement's end. A novice developer could easily overlook this.JavaScript
with
statement functions more or less the same as Pascal's and JS developer's opinions of it are just as polarized as Pascal developer's. ECMAScript 5th Edition "Strict" mode forbids its use and many implementations now emit a warning that it has been deprecated and/or suggest an alternative.D
with
statement with behavior similar to Pascal's with one notable exception: local identifiers and identifiers within the scope of the with
statement cannot have the same name. This results in a compilation error, effectively preventing the ambiguity caused by the introduction of new object members.
struct Date
{
int day;
int month;
int year;
}
void main()
{
int day;
Date today;
with (today)
{
day++; // error, shadows the local day declaration
}
}
Languages with similar features
with
.- Smalltalk cascades - multiple messages can be sent to the same object separated by a semicolon
- C#
using
statement - encapsulates the more verbose try..except..finally syntax when working with implementors of IDisposable- object and collection initializers - a more concise syntax for setting object properties at creation time
- Python
with
statement - similar to C#'s using statement, it encapsulates the try...except...finally pattern into a less verbose syntax.
Fluent Interfaces are another programming style that can be used to accomplish the same goal as
with
statements. They use varying combinations of method chaining, nested functions and object scoping along with careful method naming to produce concise, readable statements. The Date record could be rewritten as:type
Date = class
private
FDay: 1..31;
FMonth: 1..12;
FYear: 0..9999;
public
function Day(Day: integer): Date;
function Month(Month: integer): Date;
function Year(Year: integer): Date;
end;
function Date.Day(Day: integer): Date;
begin
self.FDay := Day;
Result := Self;
end;
function Date.Month(Month: integer): Date;
begin
self.FMonth := Month;
Result := Self;
end;
function Date.Year(Year: integer): Date;
begin
self.FYear := Year;
Result := Self;
end;
Then in place of the with
statement you could write:begin
today := Date.Create
.Day(25)
.Month(12)
.Year(2012);
end.
Monday, December 20, 2010
A case for testing getters, setters and properties.
Today I discovered a reason that may make it worth while to test even these. I spend most of my time working with legacy code. Often times I am uncomfortable making changes without the safety of a test harness so I'll take advantage of automatic test generation to create skeleton tests from production code that I can fill in as I work.
The tool I use for this isn't terribly smart. It just creates a class of test cases for each public method it finds in the production class its given. Each test case calls each method but leaves the Assertion up to the developer to fill in.
I had just created a new test harness from a production class I needed to modify. I ran the tests without any changes, meaning no checks were being done. I fully expected the tests to succeed since the framework I was using didn't consider an assertion-less test to be a failure. I was amazed when it did in fact fail.
The reason for the failure was a stack overflow caused by a getter returning the value of the property it was as a backing function for rather than the value of the backing field. This of course resulted in an endless recursion that quickly exhausted the call stack. This bug had never turned up in production because this was a base class that wasn't used directly and all descendant classes overrode that particular getter.
This subtle bug is more likely to happen in languages like C#, Object Pascal and Python (and several others) which hide the getter and setter calls behind a more friendly syntax that allows them to be assigned like public fields.
MyObject.PropertyValue = 123;
So even before any of the tests were verifying results they had already uncovered a bug in one of the simplest operations possible.
Friday, August 20, 2010
Bad Programming Practice #2
Misusing CTRL+V
It seems innocent enough. You write a snippet of code that causes tulips to spring up all over the computer screen. The users rejoice. Some time later they decide they want the option to see sunflowers as well. They're both flowers and your time is precious so you decide the quickest way is to copy the tulip code, and replace tulip with sunflower. Problem solved. The users rejoice again.
The years pass. Each time the users request another type of flower: CTRL+C, CTRL+V, Modify. Then one day... you discover your original tulip desktop function has the ugly side effect of slowly filling the user's hard drive with temporary files in random locations until it chokes. Only now you have 500 modifications of the same function, many of which barely resemble the original and they all have the same problem. It takes months to fix and even after you release Flower Desktop 10.0 SP1 you're still not sure if you found them all. Mean while, your users grow tired of manually hunting for randomly placed temporary files while waiting for you to fix the problem and decide to replace your program with the cool new Flaming Desktop 1.0 they just finished downloading. You flog yourself repeatedly for copying and pasting to save time when it would've only taken slightly longer to modify your tulip function to take the type of flower as a parameter. Saving you all this work hunting down evil clones of ShowTulips().
Wednesday, August 18, 2010
Bad Programming Practice #1
Hard coding a path that only exists on your development machine
MyPath = "C:\Foo\Bar";
Let's faced it. This is just lazy. It doesn't matter if you are the only one working on the project. You will eventually move to another machine or someone else will be added to the project. Next thing you know you're pulling your hair out trying to figure out why it works on one machine but not the other. By hard coding the path you've created a dependency on the exact folder structure of your hard drive.
What's worse is that you're not only forcing this on yourself or another developer but possibly the end-user as well. Save everybody some frustration and use paths relative to the main executable or if warranted, make the path configurable by the end user.