# Topic 25 - 27 ###### tags: `The Pragmatic Programmer` ## Topic 25 Assertive Programming :::info 💡 Tip: Use assertions to prevent the impossible ::: * If you think something could never happen, add code to check it. * It's also useful checks on an algorithm’s operation. > [color=#ddd] Invariant commonly in source code #### assertions and side effects Assertion should not have side effect The `.nextElement()` call in the assertion has the side effect of moving the iterator past the element being fetched, and so the loop will process only half the elements in the collection. ```ruby= while (iter.hasMoreElements()) { assert(iter.nextElement() != null); Object obj = iter.nextElement(); // .... } ``` It would be better to write ... ```ruby= while (iter.hasMoreElements()) { Object obj = iter.nextElement(); assert(obj != null); // .... } ``` #### leave assertions turned on Should not turn off the assertions after tests (in production). 1. Tests do not cover every situations. 2. Production environment is way complicated the one for development Your first line of defense is checking for any possible error, and your second is using assertions to try to detect those you’ve missed. ## Topic 26 How to Balance Resources :::info 💡 Tip: Finish what you start ::: :::spoiler Example: update customer information in files #### Buggy snippet The routines `read_customer` and `write_customer` are tightly coupled —they share the instance variable `customer_file`. `read_customer` opens the file and stores the file reference in `customer_file`, and then `write_customer` uses that stored reference to close the file when it finishes. This shared variable doesn’t even appear in the `update_customer` routine. ```ruby= def read_customer @customer_file = File.open(@name + ".rec", "r+") @balance = BigDecimal(@customer_file.gets) end def write_customer @customer_file.rewind @customer_file.puts @balance.to_s @customer_file.close end def update_customer(transaction_amount) read_customer @balance = @balance.add(transaction_amount,2) write_customer end ``` <br> #### What if the balance should be updated only if the new value is not negative ... ... the specification has changed—the balance should be updated only if the new value is not negative. They go into the source and change update_customer. ... when the code goes into production, it collapses after several hours, complaining of too many open files. It turns out that `write_customer` is not getting called in some circumstances. When that happens, the file is not getting closed. ```ruby= def update_customer(transaction_amount) read_customer if (transaction_amount >= 0.00) @balance = @balance.add(transaction_amount,2) write_customer end end ``` #### It's bad to to deal with the this in `update_custome` Three routines are coupled through the shared variable `customer_file`, and keeping track of when the file is open or not is going to start to get messy. ```ruby= def update_customer(transaction_amount) read_customer if (transaction_amount >= 0.00) @balance += BigDecimal(transaction_amount, 2) write_customer else @customer_file.close # Bad idea! end end ``` #### Instead of holding on to the file reference, we’ve changed the code to pass it as a parameter. ```ruby= def read_customer(file) @balance=BigDecimal(file.gets) end def write_customer(file) file.rewind file.puts @balance.to_s end def update_customer(transaction_amount) file=File.open(@name + ".rec", "r+") # >-- read_customer(file) # | @balance = @balance.add(transaction_amount,2) # | file.close # <-- end ``` #### In many modern languages, you can scope the lifetime of a resource to an enclosed block of some sort. ```ruby= def update_customer(transaction_amount) File.open(@name + ".rec", "r+") do |file| # >-- read_customer(file) # | @balance = @balance.add(transaction_amount,2) # | write_customer(file) # | end # <-- end ``` ::: <br /> Because the functions to open and close files are indepentent, uses of function opening files do not guarantee that the files opened would be closed. The solution here is what is mentioned in next tip, to act locally. > [color=#ddd] Cleanup function in useEffect() <br /> :::info 💡 Tip: To act locally ::: > [color=#ddd] Whoever allocates a resource should be responsible for deallocating it #### nest allocations * Deallocate resources in the opposite order to that in which you allocate them. That way you won’t orphan resources if one resource contains references to another. * When allocating the same set of resources in different places in your code, always allocate them in the same order. This will reduce the possibility of deadlock. (If process A claims resource1 and is about to claim resource2, while process B has claimed resource2 and is trying to get resource1, the two processes will wait forever.) <br /> ### Go further the concept acting locally #### objects and exceptions Encapsulate the logics of allocating and deallocating resources with constructor and deconstructor in OO paradigm. (able to make good use of exception when unsuccessfully deallocating resources) #### balancing and exceptions * Use variable scope(for example,stack variables in C++ or Rust): variables out of scope would be reclaimed which means resources would also be deallocated. * Use a finally clause in a try...catch block: deal the deallocating in the finally clause, and mind that the same resource should be able to accessed as variable in different clauses. #### when you can’t balance resources For example, `dynamic data structures`. It's one routine that will allocate an area of memory and link it into some larger structure, where it may stay for some time. Three main options to deal with this: 1. The top-level structure is also responsible for freeing any substructures that it contains. These structures then recursively delete data they contain, and so on. 2. The top-level structure is simply deallocated. Any structures that it pointed to (that are not referenced elsewhere) are orphaned. 3. The top-level structure refuses to deallocate itself if it contains any substructures. :::success Encapsulate the logics as a module: to write a module for each major structure that provides standard allocation and deallocation facilities for that structure. (this module can also provide facilities such as debug printing, serialization, deserialization, and traversal hooks.) ::: #### checking the balance Encapsulating (producing wrappers) for each type of resource, and using these wrappers to keep track of all allocations and deallocations. ## Topic 27 Don’t Outrun Your Headlights Always take small, deliberate steps, checking for feedback and adjusting before proceeding. Consider that the rate of feedback is your speed limit. You never take on a step or a task that’s “too big.” #### What’s feedback here? 1. Results in a REPL provide feedback on your understanding of APIs and algorithms 2. Unit tests provide feedback on your last code change 3. User demo and conversation provide feedback on features and usability #### What’s a too big step here? Plan a design for future maintenance or extendability The more you have to predict what the future will look like, the more risk you incur that you’ll be wrong. Instead of wasting effort designing for an uncertain future, you can always fall back on designing your code to be replaceable. Making code replaceable will also help with cohesion, coupling, and DRY, leading to a better design overall. :::info 💡 Tip: Take small steps-always ::: #### Sometimes there would be black swans ... :::info 💡 Tip: Avoid fortune telling :::