Module: good-practices-lesson

<!-- .slide: data-state="title" --> # Developing Modular Code note: === <!-- .slide: data-state="standard" --> ## What is modularity? - Software is 'built up' from smaller elements - Elements are self-contained and independent - Each element handles a specific (set of) task(s) **Simple components** build **complex behavior**. === <!-- .slide: data-state="standard" --> ## Modular code <img width="900" alt="think in building blocks" src="https://user-images.githubusercontent.com/5747405/207459058-59c88b4c-1401-428f-b28a-0ac3e72bd964.png"> === <!-- .slide: data-state="standard" --> ## What are these blocks/elements? - functions - classes - modules - libraries/packages - programs === <!-- .slide: data-state="standard" --> ## Why write modular code? To increase robustness: <img width="200" alt="testing a single module" src="./media/modular-code/testing_module.png"> - A well-designed module can be tested. - This helps keep the codebase well-functioning and bug-free. === <!-- .slide: data-state="standard" --> ## Why write modular code? To make maintenance easier: <img width="300" alt="testing a module taken from a larger project" src="./media/modular-code/testing_module_maintenance.png"> - Modular code is more readable and understandable. - Modules can be debugged separately. === <!-- .slide: data-state="standard" --> ## Why write modular code? To allow reusability: <img width="400" alt="reuse a module in another project" src="./media/modular-code/reuse_module.png"> - A module can live independent of its original context - It can be reused by another project === <!-- .slide: data-state="standard" --> ## Why write modular code? To facilitate scalability: <img height="300" alt="scalability" src="./media/modular-code/scalability.png"> <div> - Complexity remains low by design - This creates space for scaling up </div> === <!-- .slide: data-state="standard" --> ## Why write modular code? To create opportunities for innovation: <img height="300" alt="tetris shows innovation" src="./media/modular-code/tetris_innovation.png"> - Modules increase the capabilities and power of a project - Rearrange old modules for new applications === <!-- .slide: data-state="standard" --> <img width="800" alt="development speed" src="./media/modular-code/development-speed.svg"> <!-- .slide: data-state="standard" --> ## A good module... - performs limited and clearly defined tasks - has a good name <!-- .element: class="fragment" data-fragment-index="2" --> - is readable <!-- .element: class="fragment" data-fragment-index="3" --> === <!-- .slide: data-state="standard" --> ## Readability =/= shorter code Shorter: ```python= indexATG = [n for n,i in enumerate(myList) if i == 'ATG'] indexAAG = [n for n,i in enumerate(myList) if i == 'AAG'] ``` More modular: ```python= def getIndex(inputList,z): zIndex = [n for n,i in enumerate(li) if i == z] return zIndex indexATG = getIndex(myList,'ATG') indexAAG = getIndex(myList,'AAG') ``` === <!-- .slide: data-state="standard" --> ## A good module... - performs limited and clearly defined tasks - has a good name - is readable - is pure/does not have a 'state' === <!-- .slide: data-state="standard" --> ## A pure function has no side-effects: ```python= def fahrenheit_to_celsius(temp_f): temp_c = (temp_f - 32.0) * (5.0/9.0) return temp_c >>> temp_c = fahrenheit_to_celsius(temp_f=77.0) >>> print(temp_c) 25.0 ``` === <!-- .slide: data-state="standard" --> ## A stateful function changes its environment: ```python= def fahrenheit_to_celsius(temp_f): global temp_c temp_c = (temp_f - f_to_c_offset) * f_to_c_factor >>> f_to_c_offset = 32.0 >>> f_to_c_factor = (5.0/9.0) >>> temp_c = 0.0 >>> print(temp_c) 0.0 >>> fahrenheit_to_celsius(temp_f=77.0) >>> print(temp_c) 25.0 ``` === <!-- .slide: data-state="standard" --> ## Identifying opportunities for modularization === <!-- .slide: data-state="standard" --> ## Focus on readability - Modular code becomes more readable - Code is read more than it is written - Does a reader understand what the code does? - Bad readability can be a "code smell" === <!-- .slide: data-state="standard" --> ## Identify future functions - Don't Repeat Yourself (DRY): place reused code into a function - Identify potential functions by their _action_ (e.g. "plotting", "transforming", "extracting", "saving") === <!-- .slide: data-state="standard" --> ## Target nested code Nested code is a prime target for modularization: ```python= def checkTemperature(degrees): if degrees < 0: if degrees < -273: if degrees < -459: print("This temperature is impossible.") else: print("This temperature is likely Fahrenheit.") else: print("This temperature is either Celsius or Fahrenheit.") else: print("This temperature is in Kelvin, Celsius, or Fahrhenheit.") ``` === <!-- .slide: data-state="standard" --> ## Reduce nestedness by extracting modules: ```python= def validTemp(degrees): if degrees < -459: return FALSE return TRUE def checkTemperature(degrees): if not validTemp(degrees): return "invalid temperature" if degrees < 0: if degrees < -273: print("This temperature is likely Fahrenheit.") else: print("This temperature is either Celsius or Fahrenheit.") else: print("This temperature is in Kelvin, Celsius, or Fahrhenheit.") ``` === <!-- .slide: data-state="standard" --> ## Let tests help you - Write tests for each individual module - Use the test-writing procedure to look critically at the module's function: - Is the input/output clear? - What can you not yet test? Extract it into a new module.