Design Bite - Basic Structure
Monolithic line-counter
"The only thing worse than starting something and failing ... is not starting something"
- Seth Godin
1.0 Introduction
This DesignBite sequence was inspired by BuildOn project TextFinder.
As that project is designed and implemented, a number of design decisions are made, consciously or unconsciously.
Each of these pages addresses one answer to questions about structure.
To make discussion pragmatic and concrete, we implement a program that evaluates the number of lines
in text files. Processing is quite simple so it allows us to see how each
structure alternative is implemented.
We consider both package structure and logical structure, e.g., the functions and structs used to order
design and implementation. In this Basic Structure page, all code is implemented in a single package and
a single struct provides all of the organization for processing.
2. Application Structure - Basic
This structure is monolithic, e.g., all code is packaged in a single file and there is only one struct
used to implement processing, e.g., counting lines of code in files specified on the command line.
Figure 2. Basic Pkg Structure
Basic Monolithic Structure
Program's operations - read input, process data, output information - are implemented in
a single package.
Pros:
- Simple, easy to create
- Only one piece to track
Cons:
- Everything is compiled for any changes
- As content grows becomes hard to understand and test
Basic Code Repository
Source
/////////////////////////////////////////////////////////////
// basic_structure::main.rs //
// //
// Jim Fawcett, https://JimFawcett.github.io, 07 Mar 2021 //
/////////////////////////////////////////////////////////////
/*
BasicStructure
- Demonstrates simplest form of structure: everything, e.g.,
input, computation, and output, in one package.
- It counts the number of lines in a file specified on the
command line.
*/
#![allow(dead_code)]
use std::fs::*;
use std::io::{Read, Error, ErrorKind};
/*-- part of input processing --*/
fn open_file_for_read(file_name:&str)
->Result<File, std::io::Error> {
let rfile = OpenOptions::new()
.read(true)
.open(file_name);
rfile
}
/*-- part of compute processing --*/
fn read_file_to_string(f:&mut File)
-> Result<String, std::io::Error> {
let mut contents = String::new();
let bytes_rslt = f.read_to_string(&mut contents);
if bytes_rslt.is_ok() {
Ok(contents)
}
else {
Err(Error::new(ErrorKind::Other, "read error"))
}
}
#[derive(Debug)]
struct Basic {
name: String,
file: Option<File>,
lines: usize,
}
impl Basic {
fn new() -> Basic {
Basic {
name: String::new(),
file: None,
lines: 0,
}
}
/*-----------------------------------------------------
Input processing
*/
fn parse_cmdln() -> Vec<String> {
let cl_iter = std::env::args().into_iter();
let args: Vec<String> = cl_iter.skip(1).collect();
args
}
fn show_cmdln(args: &Vec<String>) {
if args.len() == 0 {
return;
}
print!("\n {}", args[0]);
for arg in &args[1..] {
print!(", {}", arg);
}
}
fn input(&mut self, name: &str) {
self.name = name.to_string();
let rslt = open_file_for_read(name);
if let Ok(file) = rslt {
self.file = Option::Some(file);
}
else {
print!("\n can't open file {:?}", name);
}
}
/*-----------------------------------------------------
Compute processing
*/
fn compute(&mut self) {
if let Some(file) = &mut self.file {
let rslt = read_file_to_string(file);
if let Ok(contents) = rslt {
self.lines = 1;
for ch in contents.chars() {
if ch == '\n' {
self.lines += 1;
}
}
}
}
}
/*-----------------------------------------------------
Output processing
*/
fn output(&self) {
print!("\n {:4} lines in {:?}", self.lines, self.name);
}
}
/*---------------------------------------------------------
Executive processing
*/
fn main() {
print!("\n -- counting lines in files --\n");
let mut basic = Basic::new();
let args = Basic::parse_cmdln();
for name in args {
basic.input(&name);
basic.compute();
basic.output();
}
println!("\n\n That's all Folks!\n\n");
}
Output
cargo run -q ./src/main.rs cargo.toml
-- counting lines in files --
120 lines in "./src/main.rs"
10 lines in "cargo.toml"
That's all Folks!
cargo.toml
[package]
name = "basic_structure"
version = "0.1.0"
authors = ["James W. Fawcett"]
edition = "2018"
# See more keys ...
[dependencies]
3. Epilogue
The fourh design alternatives considered here:
- Monolithic Structure
- Factored Structure
- DataFlow Structure
- TypeErase Structure
- PlugIn Structure
are progressively more flexible, eventually resulting in reusable components, but also increasingly
complex. Where you settle in these alternatives is determined by design context. Is this a
one-of-a-kind project that you want to finish quickly or is it
heading for production code that will be maintained by more than one developer?