March 16, 2007

Configurable Defaults

Configurable defaults?
I know a 100% reliable test to distinguish amateur product manager from the professional. When amateurs hear about loadable parameters (shown in my previous post), they immediately urge a closest developer to make those parameters editable from the application's user interface. Professionals never do that mistake.

Everybody's got something to hide
Should I explain? Not all parameters have to be available for user. Some values are result of big research effort, such as value ProcessTimeOut stored in the Registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Asr.
It's better to protect users from the accidental change of such values. Unfortunately, in many products I saw too much configurable parameters irrelevant to everyday user needs.

Cost of configurable parameters
Configurable values need not only load function, but also its store counterpart. Coding efforts increase in two times, and testing efforts increase in four times (please think why). In addition, you need to develop and test user interface controls to change configurable values. The cost difference between configurable values and semi-constants is significantly larger than the cost difference between semi-constants, encapsulated decisions, literal aliases, and parameter descriptors discussed in my previous posts. That's why parameters should be configurable only when the ability to configure them brings profit to at least 80% of your target auditory. Do not urge people to pay for features they do not need.

As a rule, prefer to process things automatically. The user is often the weakest part of the information technology processes. Avoid the user involvement in the processing, unless it is necessary.

Quiz
Give three examples when user-configurable defaults are preferable to non-configurable defaults. Note, I mean defaults, not documents created by the user.

March 15, 2007

From Encapsulated Values to Semi-Constants

Decisions to semi-constants
My previous post is about encapsulated decisions. In trivial case, they are just encapsulated values - constants hidden behind the function interface. You can make a step further and make them loadable from some storage. I call such parameters semi-constants, because their values are predefined, but the parameters themselves are not const in the C++ sense.

Listing 1. Example of semi-constants
class LoadableParameters
{
public:
Parameters() { Load(); }
const char * ProductWebSite() const { return web_site_url.c_str(); }
unsigned long HashTableSize() const { return hash_table_entries_number; }
private:
std::string web_site_url;
unsigned long hash_table_entries_number;

void Load()
{
Repository storage;
const char key_name[] = {"product_attributes"};

const char site_value_name[] = {"web_site"};
if ( ! storage.LoadString( key_name, site_value_name, &web_site_url ) )
{
const char web_site_url_default[] = {"http://myCompany.com"};
web_site_url = web_site_url_default;
}

const char hash_value_name[] = {"web_site"};
if ( ! storage.LoadUnsignedLong( key_name, value_name, &web_site_url ) )
{
const unsigned long hash_table_size_default = 37;
hash_table_entries_number = hash_table_size_default;
}
}
};


Application of semi-constants
Semi-constants are well suited for use in the families of similar products, when the value of particular parameter is different for different products. For example, the software accompanying the hardware device can have loadable reference to the device's web page. The plug-in for the host program (such as add-in module for Microsoft Office) can have loadable lowest supported version number of the host application. If features of your product are implemented as separate modules, you may make loadable list of features that are available for particular flavor of the product: product "business" has features A, B, and C, product "home" has features C and D, product "big money" has A, B, C, and D.

Where to store semi-constants
In a popular scenario, the product installation procedure stores values of the parameters inside the Windows Registry, or in the disk file. At startup, the program reads those values.
Modern network-oriented scenarios assume that values are stored on the remote server. The product support team owning the server may change the values invisibly for product users.
If your product uses the database for the customer's data, you may add a table for storing parameters of your application.
Some systems assume that user's credentials (user name and password) are stored on smart cards.
Standard way of keeping locale-specific data, such as text strings translated to target languages is to store them inside language packs as Windows Resources.

By default
Note that attempt to load a semi-constant may fail (for example, because the server is temporarily disconnected from the network). You need provide default values using named constants or encapsulated decisions described above.

Delayed load
If you will made the instance of LoadableParameters class from Listing 1 the global variable or the member of the class instantiated as a global variable, parameters will load at the program startup. If the loading of semi-constants involves long-executing procedures (such as loading data from the Internet), you may want to postpone actual loading to the moment in time when parameters will asked for a first time.

Listing 2. Delayed load of semi-constants
LoadableParameters & LoadableParametersInstance()
{
// constructed at first call of LoadableParametersInstance()
static LoadableParameters single;

return single;
// destroyed at exit from the program
}

March 7, 2007

From Aliases to Encapsulated Decisions

This post continues the theme "Pass names, not values".

Listing 1. Another style of grouping aliases
{
const TCHAR WEEKLY_INSTRUCTIONS[] = {"\\\\ServerName\\FolderName\\Instructions.txt"};
const DWORD QUERY_ATTRIBUTES = 0;
const DWORD DO_NOT_SHARE = 0;
const LPSECURITY_ATTRIBUTES HANDLE_CANNOT_BE_INHERITED = NULL;
const DWORD NOT_APPLICABLE = 0;
const HANDLE DO_NOT_USE_TEMPLATE = NULL;

LPCTSTR file_name = WEEKLY_INSTRUCTIONS;
DWORD access_mode = QUERY_ATTRIBUTES;
DWORD share_mode = DO_NOT_SHARE;
LPSECURITY_ATTRIBUTES security_attributes = HANDLE_CANNOT_BE_INHERITED;
DWORD actions_to_take_regarding_file_existence = OPEN_EXISTING;
DWORD attributes_and_flags_or_security_quality_of_service = NOT_APPLICABLE;
HANDLE template_file_supplying_attributes = DO_NOT_USE_TEMPLATE;

HANDLE hFile = ::CreateFile(
file_name, access_mode,
share_mode, security_attributes,
actions_to_take_regarding_file_existence,
attributes_and_flags_or_security_quality_of_service,
template_file_supplying_attributes
);
}

First part of the scope defines the aliases for literals:
• application-defined path;
• and operating system values that do not have textual aliases.
Note that aliases are defined as const. Use of const prevents aliases from accidental change by assignment, like
NOT_APPLICABLE = expression;


Second part initializes parameter descriptors. According to safety rule, if the function has the pointer parameter, and does not modify the value of that pointer, the author should declare the pointer as const. The CreateFile function prototype violates this rule for 4-th parameter, represented in our example by argument security_attributes: according to CreateFile specification, it is an input-only pointer, but it is not declared as const. That looks a little bit odd by contrast with the first parameter file_name: input-only pointer, which is const. Oh, that Win32 API…

Third part of the scope is the function call.

Local aliases to global aliases
Sometimes you may want to share a literal between different scopes. The simplest way to share a literal (more precisely, the alias) is to make it the global alias:
const TCHAR WEEKLY_INSTRUCTIONS[] = {"\\\\ServerName\\FolderName\\Instructions.txt"};

// scope 1
{
// ...
LPCTSTR file_name = WEEKLY_INSTRUCTIONS;
// ...
}

// scope 2
{
// ...
LPCTSTR file_name = WEEKLY_INSTRUCTIONS;
// ...
}


Visibility
Scopes, which reuse the alias, may be parts of the one file, or they may be spread between modules. The alias is visible from the module, if the use of the alias in the module does not contradict to programming language rules (in other words, if the compiler does not complain that the alias name is unknown). There are several different flavors of global aliases, distinguished by an alias visibility.

Popular flavors of global aliases
VisibilityHeader (.h)Implementation (.cpp)
Module local constant   const int x = 1;
Shared constantextern const int y /*= 2*/;#include "MyHeader.h"
const int y = 2;
Implicitly copied constantstatic const int z = 3;#include "MyHeader.h"


Aliases to encapsulated values
If words "global variable" scare you, hide literals (and arrays of literals) inside inside encapsulated values - functions returning constants, like this one:
LPCTSTR WeeklyInstructionsFilePath() throw() 
{
return "\\\\ServerName\\FolderName\\Instructions.txt";
}


Compound encapsulated decisions
Encapsulated values are trivial forms of encapsulated decisions. Encapsulated decisions that are not trivial include at least two parts:
• the function call
• and the preparation of well-known reusable arguments for this call.
Compound encapsulated decisions may use other encapsulated decisions.
The Listing 2, which derives from the Listing 1, shows an example.

Listing 2. Encapsulated file opening.
const TCHAR WEEKLY_INSTRUCTIONS[] = {"\\\\ServerName\\FolderName\\Instructions.txt"};
const DWORD QUERY_ATTRIBUTES = 0;
const DWORD DO_NOT_SHARE = 0;
const LPSECURITY_ATTRIBUTES HANDLE_CANNOT_BE_INHERITED = NULL;
const DWORD NOT_APPLICABLE = 0;
const HANDLE DO_NOT_USE_TEMPLATE = NULL;

HANDLE OpenWeeklyInstructionsFileExclusively( DWORD access_mode )
{
LPCTSTR file_name = WEEKLY_INSTRUCTIONS;
DWORD share_mode = DO_NOT_SHARE;
LPSECURITY_ATTRIBUTES security_attributes = HANDLE_CANNOT_BE_INHERITED;
DWORD actions_to_take_regarding_file_existence = OPEN_EXISTING;
DWORD attributes_and_flags_or_security_quality_of_service = NOT_APPLICABLE;
HANDLE template_file_supplying_attributes = DO_NOT_USE_TEMPLATE;

return ::CreateFile(
file_name, access_mode,
share_mode, security_attributes,
actions_to_take_regarding_file_existence,
attributes_and_flags_or_security_quality_of_service,
template_file_supplying_attributes
);
}

HANDLE OpenWeeklyInstructionsForQuery()
{
DWORD access_mode = QUERY_ATTRIBUTES;
return OpenWeeklyInstructionsFileExclusively( access_mode );
}

HANDLE OpenWeeklyInstructionsForRead()
{
DWORD access_mode = GENERIC_READ;
return OpenWeeklyInstructionsFileExclusively( access_mode );
}


Encapsulated decisions as design specification
Despite their simplicity, encapsulated decisions are in fact very valuable part of your program: they concentrate your constructive thought. Interestingly, those functions are highly reusable across modules and projects.

Popular flavors of encapsulated decisions
VisibilityHeader (.h)Implementation (.cpp)
Module local function   int x() throw() { return 1; }
Shared functionint y() throw();#include "MyHeader.h"
int y() throw() { return 2; }

Implicitly copied function
inline z() throw() { return 3;}#include "MyHeader.h"


Listing 3. Poor man's encapsulated decisions
HANDLE OpenWeeklyInstructionsForQuery()
{
return ::CreateFile(
"\\\\ServerName\\FolderName\\Instructions.txt",
0, 0, NULL, OPEN_EXISTING, 0, NULL
);
}

HANDLE OpenWeeklyInstructionsForRead()
{
return ::CreateFile(
"\\\\ServerName\\FolderName\\Instructions.txt",
GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL
);
}


Cost of typing against the cost of maintenance
What is better: 15 lines of the code in the Listing 3 or 36 lines of the code in the Listing 2?
Many people rushing to finish already late project say "Of course, 15 lines - it is shorter!" Indeed, the Listing 3 contains less code than Listing 2. However, the understanding of the Listing 3 requires much more time than typing of 21 explanation lines: you need to refresh in your memory the description of CreateFile (about of 170 lines), then you need to compare implementations of OpenWeeklyInstructionsForQuery and OpenWeeklyInstructionsForRead parameter by parameter.

Summary
If your code contains multiple calls of CreateFile or any other function with literal arguments, think about encapsulating those calls inside product-specific functions having less number of arguments. Use parameter descriptors and aliases inside encapsulated decisions in order to make your code comprehensible.