Skip to content

Instantly share code, notes, and snippets.

@dsilfen-handy
Last active October 17, 2016 13:13
Show Gist options
  • Save dsilfen-handy/33ca45f2c852a5db12d69127714240a8 to your computer and use it in GitHub Desktop.
Save dsilfen-handy/33ca45f2c852a5db12d69127714240a8 to your computer and use it in GitHub Desktop.
Notes for Pragmatic Programmer chapter 6

Pragmatic Programmer: Chapter 6

What to think about while writing code

Programming By Coincidence

  • Untrue assumptions are like landmines, and tend to blow up in our faces at inopportune times.
  • Before changing code, one should have a good handle understanding of how the current implementation works.
  • Accidents of Implementation: Relying on behavior that is inherently unreliable. EX: undocumented errors, boundary conditions, call order
    • When accidents of implementation happen, we can end up with surprising errors in one end of the application when the other is fixed.
    • These types of errors can be common in rails Models since we update their state throughout a request.
      • There are many instances of ActiveRecord::Base#reload called in our application. When we have to explicitly reload a model in a procedure, it can create downstream dependencies where methods expect a fresh object, but due to a change upstream we now have stale data that could lead to errors.
      • has_many relations also face issues when we do not use the collection constructors.
# Good

# items now has all items for this model
my_model.items.create!(some: :attribute) 

# Bad

# called up here so rails caches the results
my_model.items

new_item = Item.create(model_id: my_model.id, some: :new_attribute)
my_model.items.include? new_item # false
my_model.items(true).include? new_item # true
  • Accidents of Context & Implicit Assumptions: Relying on assumptions that might not be true.
    • Assumptions about the country where most of our customers live…

How to Programming Deliberately

  • Always be aware of what you are doing (try and only act with explicit intent).
  • Attempting to build an application you don’t understand or to use a technology you are not familiar with is an invitation for trouble.
  • Rely only on reliable things.
  • Document your assumptions
  • Prioritize your effort: spend time on the most important things first
  • Don’t let existing code dictate future code. If we see something has been made obsolete or has not been updated to the new business policy, fix it.

Algorithms & Big O

  • It is important to always strive for efficiency and try to design efficient algorithms
  • Big O is a useful tool for expressing the worst-case runtime a given algorithm.
    • Be aware of the shortcomings of Big O, since we only show the highest order terms in big O, then O(n^2) != O(n^2), since low order terms still effect runtime.
  • Use common sense estimation to decide when it is right to spend time optimizing your algorithm
    • Simple Loops: Okay when you know your input will not be too large for the application. These will typically be O(n).
    • Nested Loops: Probably a sign that there is a better way, but can be useful. These will typically be O(n^2).
    • Binary Chop: Halves the group in consideration through each iteration, performance is likely O(lg(n)).
    • Divide and Conquer: Algorithms that partition work, and then work on the sets separately until it finds a result. These typically perform in O(n lg(n))
    • Combinatoric: Looking at the permutations of things, usually performed in O(n!). Try to steer clear of these.
  • Be certain when testing code to try it with variable input sizes.
  • Use Profilers to figure out if its the right time to optimize your algorithm. No need to spend the effort if the gain is not noticeable.

Refactoring

  • When code is wrong, fix it.
  • When to refactor:
    • Duplication: Promote Dry Design
    • Nonorthogonal-Design (Orthogonal code is lovely coupled and does not have wide spread effects when changed): When coupling is forming
    • Outdated Knowledge: New business requirements
    • Performance: Try and keep site uptime at 99.999%
  • Updating earlier decisions is an exercise in pain management.
  • Do not try to add functionality while refactoring.
  • Have confidence in your tests before making changes.
  • Make small steps, but push them often and test after each one.

Code Thats Easy To Test

  • Test your individual components so you can have confidence in the system
  • Testability should be a consideration in your initial design
  • Unit tests verify behavior and confirm the contract
  • Confirm the contract: pass not only the best case scenario, but also completely unexpected scenarios and ensure your code errors. Confirm the contract is as expected.
  • Where should tests live:
  • Build a test window:
    • Ensure you have a way to confirm behavior in the field.
    • Many services have an /is_alive to check simple things like the git SHA of the running code and a bool representing a database connection.
    • Use logs effectively to document input/output
  • Ensure we promote a culture of testing.
  • If we do not test our code, our users will.

Evil Wizards (Wizard like IssueWizard)

  • When we use static code generators, we must retain the knowledge of what they are providing for us.
  • In rails, our generators are fairly simple and its clear the benefits of using them.
  • Wizards that write code for you can save you the time of churning out boilerplate.
  • Wizards should not allow you to “program by coincidence”, you should still fully understand how your code is running.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment