Saturday, November 19, 2016

My take on software quality, common pitfalls, and the mindset for success.

Common Pitfalls

This article applies to all branches of software development, and is my view on software quality, based on 12 years of experience as a software engineer.  We will be looking at common pitfalls, and how they can be addressed by a particular line of thought or strategy. 
What makes software projects have poor quality and grind to a halt, or have costly production defects?  Based on industry feedback, some of the following symptoms are causes of software failure: 
  1. Insufficient attention to detail and care for the code.  (Patch on patch, fixing only immediate symptom, without understanding root cause). 
  2. Lack of proper object-oriented architecture (resulting in ever increasing complexity of code). 
  3. Copy and Pasted code (violation of DRY)
  4. Improper design (violation of SOLID)
  5. Poor coding practices (huge methods, too many conditional statements or "accidental complexity")
  6. Separation of concerns missing. (Grouping many duties into one method, class, object, event or variable. ). 
  7. Unclear definition of concern separation. (Not sure what is the single responsibility of each class, method, variable, object, etc) 
  8. Unclear / ambiguous naming of variables / objects.  Names not specific enough, resulting in need for comments. 
  9. Fear of refactoring.  Fear of making changes.   (Afraid because we are not sure what is broken after making a change). 
  10. Lack of or complete absence of unit tests, resulting in no way to cheaply have assurance of correct behaviour upon making changes. 
  11. Inability to "Step Back" and ask really difficult and uncomfortable questions.  (Should we even be doing this?  What's the benefit?)  - Tunnel Vision. 
  12. Lack of "Ownership" of the code.  Afraid to change.  Unwilling to figure out how it works.
  13. I'll remember to do it later.  It's fine for now. 
  14. I really want to learn this new thing, so i'll use it, even though it's doesn't quite fit. 
  15. Accidental complexity allowed to continue unchecked. We keep adding features without thinking about design and making the code easy to re-use and grow. 
  16. The "Not enough code" syndrome.  Now we all know that "less code is better", mainly for maintenance reasons.  But actually, a lot of the time, you need to have a lot of code in order to fully address the requirement, and do a good job implementing the feature you envisioned.  Things are often simple on the surface, but you need to have a great UX and cover all the edge cases.  So you might need to write a lot of code.  Just make sure to architect it in a way that makes it all separated by responsibility.  Don't compromise on that.  Ask yourself: "Does this software suck because we didn't fully flesh out how it should work?" 

Strategy for Success

So how do we not only avoid the above grim situation, but on the contrary: achieve the complete opposite?  We reverse each point above into its' positive counterpart: 
  1. Meticulous attention to detail in every line of code we write. (We should be proud and confident about our code.  We can easily speak about the decisions we made and their justifications. Not:  "it sorta works, i guess". ). 
  2. Think through the design of every feature first.  Even if its' a minor feature.  Examples:
    1. Do i put this in the view controller?
    2. Is this likely going to be reused later by someone?
    3. Can it be made completely generic?  What's the extra effort in doing so? 
    4. Does adding this increase the complexity of the class where we are adding this? (and thus it's $$$ maintainability)
    5. By adding my code, did i just make the object do more than 1 task?
  3. Is this task already done somewhere else?  Is it in multiple places?   Can i take it out of those various places and instead put it in one place, and reuse that?   
    1. Doing this for every feature you implement continuously improves the overall strength of the code base. 
    2. Notify other developers of these reusable resources:  building up your team's "Toolbox".    ("Hey, i made this category which makes sure things are done on the main thread, with one line of code!") 
  4. Refer to wikipedia, on SOLID definition.  (https://en.wikipedia.org/wiki/SOLID_(object-oriented_design))
    1. In Pull requests, the reviewer, can asses each of SOLID's properties, to check for violations.  Fix at PR stage. 
  5. Enforce the following guidelines:
    1. Methods can only be 20 lines of code or less. 
    2. Classes must be 700 lines of code or less.  (If exceeding this, start making component classes out of your class with Roll-Up objects, that can just be used with a few lines of code - Facade pattern). 
    3. Nesting (If, switch, loops, blocks), not more than 2 levels deep.  (3 levels deep or more = you must break it up into separate methods). 
    4. Single responsibility for every object.  Few exceptions are:  Facade class which manages other objects, but know little to nothing about how they work internally. 
    5. Rely on abstractions in areas which are likely to change.  (In other words: if something is likely to change, put an interface around it, with a simplified entry point to the functionality).  I believe abstracting everything is a waste of time though.  So the judgement call is:  What is most likely to change? 
  6. What is the primary responsibility of this class (every class)?  The answer should be provided in one short sentence, always. 
  7. We should first define what the different "Concerns" are, and then consciously place classes into those buckets.  
    1. For example, responsibility can be separated into into: 
      1. Networking
      2. Navigation
      3. Persistence
      4. Generic Views/Controls
      5. Business Specific Views/Controls (can reuse Generic Views internally) 
      6. View Controllers (screens comprised of components) 
      7. Components.  (More complex than just a view or control.  It's a business-specific UX piece that does a single job). 
      8. Utilities.  (Many categories here.  These are always generic and reusable.  Do not know about business rules, that are likely to change). 
      9. Configuration
      10. Styling (Style sheet)
      11. Localization
      12. Accessibility
      13. Analytics
      14. Validation
      15. Alert / Dialog utilities
      16. Resource / media access  (really easy way to access images from network or local)
  8. "Take that comment and make it a method".  
    1. Then put the code into that method.  
    2. Voila = self-documenting code. 
    3. It's OK to have long method names (within reason).  
    4. If the name is too long, then is it doing more than one thing??
  9. The more you do refactoring as you implant features, the easier it will become.  Just like any skill - it's acquired over time.  I believe refactoring should be constant, as development continues. 
  10. Let's say you have 600 unit tests.  You can run them overnight on all OS versions, example: iOS 8.0, .1, .2, .3, iOS 9.0, .1, .2, .3. iOS 10, etc...   You will uncover edge cases on specific iOS versions using these tests.  You would have to have an entire QA department, and QA manager distributing work for them, to do this manually.  The cost would be 100 times or 1000 times more. 
    1. There definitely is a trade-off overhead in managing the tests, and setting up the CI for the first time.  But the act of managing the tests does give you extra insight into the state of the code.  (Reality check - what did i just break?, or how can the architecture be improved?). 
  11. The best code is No-Code  (guaranteed bug-free!!).  If you can avoid implementing something, just make the decision not to, and justify it objectively.  Some business requirements go away by themselves.  This is because assumptions get dispelled, and stakeholder input causes initial claims to be invalidated, making some requirements obsolete.  Example:  Your customer later tells you: "We actually can't have this feature because it violates privacy regulation number N".   
  12. "I just have to implement this one feature", i will just do it.   I can first check whether it's done similarly somewhere else.  I can ask another developer about it first.  Maybe someone in the department already delved into a similar task.  I shouldn't be afraid of making larger changes than what I planned.  Refactoring can improve the maintainability of the code. 
  13. Fact: The cost to fix bugs grows exponentially the later they are detected.  Example:
    1. Cost to fix in dev: $10
    2. Cost to fix in QA: $100
    3. Cost to fix in UAT: $1000
    4. Cost to fix in production $10000 or more.

      It's also better to leave the code in the best state possible, so other developers don't have the cost overhead of asking about what's going on.
  14. Using tech just because you want to put it on your resume is selfish, because you can just implement it simpler using standard tools, but using SOLID.  "A new co-op student should be able to figure it out".  
  15. Keep making easy to use facades in the code, abstracting away complexity.  Keep refactoring the code until it fits into the following set of guidelines: 
    1. Methods can not be longer than 20 lines of code. 
    2. Classes can not be longer than 700 lines of code. 
    3. Nesting of If statements, loops or scope levels can not be more than 2 levels deep. 
  16. Perhaps the implementation could be improved.  Sometimes this includes a lot of effort, R&D, or sheer brute force coding, but as long as the code is structured properly, it's OK to have a lot of code.  (Just think of a piece of software like MS Office, and how much code it has).  
Learn how to create your own Programming Language here!!

The Mobile Startup: Episode 5: Some thoughts about tech, and work.

Knowing that You're Bad! I think that if you have never thought of yourself as a bad engineer before, then you are probably a Bad e...