about
3/07/2022
Basic Structure

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:

  1. Simple, easy to create
  2. Only one piece to track

Cons:

  1. Everything is compiled for any changes
  2. 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:
  1. Monolithic Structure
  2. Factored Structure
  3. DataFlow Structure
  4. TypeErase Structure
  5. 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?
  Next Prev Pages Sections About Keys