Today’s programming for maintainability snippet comes from a (wonderful) Rust library I was using the other day, built, which lets you embed build information into your binaries. Things like the compiler version, git HEAD commit, build time, etc. It’s a wonderful library and if you need that sort of thing I recommend it.
It’s a code generator, which spits out a built.rs
file which you with your source. The only problem is, when you compile it, you get errors like:
warning: constant is never used: `CFG_POINTER_WIDTH`
--> /home/lane/Downloads/built/example_project/target/debug/build/example_project-5b857604edf6b87a/out/built.rs:115:1
|
115 | pub const CFG_POINTER_WIDTH: &str = r"64";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
and so on for each variable in the file. No problem, I thought, I’ll just open a PR. (because one should always upstream changes) All I have to do is add a #[allow(dead_code)]
attribute in front of every attribute declaration.
The code, however, has this:
w.write_all(
b"/// If the crate was compiled from within a git-repository, `GIT_COMMIT_HASH` \
contains HEAD's full commit SHA-1 hash.\n",
)?;
writeln!(
w,
"pub const GIT_COMMIT_HASH: Option<&str> = {};",
&fmt_option_str(commit)
)?;
repeated for (most of) the variables. If you’re unfamiliar with Rust, what this is doing is formatting those two lines of text into a “writer” (i.e. to a file).
Which meant that in order to add the attribute, I’d have to edit the writeln!
for every variable - ultimately only about thirty or so, but enough to make me pause and wonder if there was a better way (really, anytime you have to do anything more than twice, you should stop and think).
This is an example of a violation of the “Don’t Repeat Yourself” principle - In this case, what we’re repeating was the structure:
/// <some docs>
pub const <variable name>: <variable type> = <variable value>;
which, in this case, I wanted to change to:
/// <some docs>
#[allow(dead_code)]
pub const <variable name>: <variable type> = <variable value>;
So, my plan is to add abstraction between the semantic layer of “this is a variable with this value,” and the actual formatting of that code. With the writeln!
code above, both of these concepts were combined in a single line of code - but, we could make (for example) a macro:
macro_rules! write_variable {
($writer:expr, $name:expr, $datatype:expr, $value:expr, $doc:expr) => {
writeln!(
$writer,
"#[doc=\"{}\"]\npub const {}: {} = {};",
$doc, $name, $datatype, $value
)?;
};
}
such that now I can define a variable like:
write_variable!(
w,
"GIT_COMMIT_HASH",
"Option<&str>",
fmt_option_str(commit),
docs,
);
and I can make the original change I wanted to via:
$writer,
- "#[doc=\"{}\"]\npub const {}: {} = {};",
+ "#[doc=\"{}\"]\n#[allow(dead_code)]\npub const {}: {} = {};",
$doc, $name, $datatype, $value
Of course, there is a balancing act, between having too few abstractions (which can make code tedious to change and prone to copy/paste bugs) and having too many (which can make code difficult to read).
When writing new code especially, it can be difficult to predict what the right abstractions to write are. It involves predicting the future, and so there is a large emphasis on “being agile” and “moving fast and breaking things.” Fortunately, in this case, the codebase was already somewhat established, and has had several features added already - this lets us look at what some of “the future” was, and we can be more confident in our changes.
This blog series updates every week at Programming for Maintainability