From my university experience, my personal projects, and my volunteer work on web apps and tutoring, I have learned that developing software is like a battle. I know conflict is a tired metaphor but stick with me for a bit. In this metaphorical battle the programmer struggles against numerous and varied adversaries. Among these foes are functional constraints like “I must get the computer to render a convincing ocean at 60+ frames per second,” logistical constraints like “I need to communicate this really complex idea quickly to my teammates,” and operational constraints like “I need other developers to be able to work with this code base in the future.” It is this last ostensible category, and indeed this last example, that begets the need for clean code.
“Clean code” is simply quickly comprehensible code. It tends to be free of “smells” which are indicators that your code could be simpler, more descriptive, or simply more intuitive. Is my code pristine, does it smell perfectly fresh? No, of course not, and I found a reasonably comprehensive article to benchmark against so I can show you why not. Let’s look at a simple piece of code I wrote to fill a two channel (two numbers stored per pixel) texture with random data and let’s compare this code against the article.
fn create_seed() -> Image {
let texture_extent = Extent3d {
width: TEXTURE_SIZE.x,
height: TEXTURE_SIZE.y,
depth_or_array_layers: 1
};
let mut data = Vec::new();
let channels = 2;
let size = channels * TEXTURE_SIZE.x * TEXTURE_SIZE.y;
for _ in 0..size {
data.push(fastrand::u8(0..=0xff));
}
// We only need two channels for the seed.
Image::new(
texture_extent,
TextureDimension::D2,
data,
TextureFormat::Rg8Unorm,
)
}
This is littered with under-descriptive names (#28) “size,” “data”. The single comment isn’t actually in the right place (which would probably be above channels), and doesn’t explain why (#1 in the code smells list) I’m pushing a bunch of random data into the image. I’m sure there are more things beside that that I’m blind to as well (I am looking at my own code after all). And this is after one refactor. Originally, this code was part of another function which created three different images of varying formats (doing multiple things is discussed in #9). Even now that other function contains duplicated code in the form of an identical texture_extent definition (#2 in the list). So I have terrible code in some respects. When under time pressure, it can be difficult to write clean code, and I still struggle to do so when I’m quickly prototyping.
That said, I manage some things better than I used to. One particularly clear article clearly explains a simple technique for clean code in Rust. It tells us how to use built-in functions to avoid the “nestiness” of rust. What do I mean by “nestiness”? Well everything is a block in Rust, functions, match clauses, if-let statements, loops, closures (lambdas), structure definitions (which can be nested), structure declarations, trait implementations, you get the picture. With all those opportunities, it’s easy to nest more than 3 blocks deep if you aren’t careful. The article clearly discusses one way to deal with this is by using functions which call other functions under certain conditions. The standard library is full of these “adapters” and utility functions. I used adapter utility methods often in my citygen project for a previous course. I wouldn’t say that the code in citygen is clean overall, but I did work hard to avoid significant nesting. See the example below. (Still has poor names, oh well.)
// These are the x start and end pairs for the grid rectangles
let x_bounds = square_x_starts
.into_iter()
.zip(square_x_ends.into_iter());
// These are the y start and end pairs for the grid rectangles
let y_bounds: Vec<(f64, f64)> = square_y_starts
.into_iter()
.zip(square_y_ends.into_iter())
.collect();
This post is long enough now, but to close I’ll say from looking at code around the internet, and at my own, it’s is easy to criticize, but hard to actually fix. The difficulty is exacerbated when you’re under the pressures of time constraints or you’re struggling to meet functional constraints. Hopefully in the future I can build in the time necessary to step back from the code and really check it for numerous code smells. By spending more time I might be able to fend off the adversary of deadlines long enough to finally emerge victorious over the notorious squad of code smells.
Thanks for reading!