The case shown in the sample code can usually be reduced to a single if
statement. On many systems, the file-open function will return an invalid value if the file doesn't already exist. Sometimes this is the default behavior; other times, it must be specified via an argument. This means the FileExists
test can be dropped, which can also help with race conditions resulting from file deletion between the existence test and file opening.
file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
DoSomething(file);
} else {
DefaultAction();
}
file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
DoSomething(file);
} else {
DefaultAction();
}
This doesn't directly address the abstraction-level-mixing issue as it sidesteps the issue of multiple, unchainable tests entirely, though doing away with the file existence test is not incompatible with separating abstraction levels. Assuming that invalid file handles are equivalent to "false" and file handles close when they go out of scope:
OpenFileIfSomething(path:String) : FileHandle {
file = OpenFile(path);
if (file && SomeTest(file)) {
return file;
}
return null;
}
...
if ((file = OpenFileIfSomething(path))) {
DoSomething(file);
} else {
DefaultAction();
}
OpenFileIfSomething(path:String) : FileHandle {
file = OpenFile(path);
if (file && SomeTest(file)) {
return file;
}
return null;
}
...
if ((file = OpenFileIfSomething(path))) {
DoSomething(file);
} else {
DefaultAction();
}