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
| Visibility | Header (.h) | Implementation (.cpp) |
|---|---|---|
| Module local constant | const int x = 1; | |
| Shared constant | extern const int y /*= 2*/; | #include "MyHeader.h" const int y = 2; |
| Implicitly copied constant | static 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
| Visibility | Header (.h) | Implementation (.cpp) |
|---|---|---|
| Module local function | int x() throw() { return 1; } | |
| Shared function | int 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.
No comments:
Post a Comment