Mjanaging Tjechnical Djebt – What is technical debt? – Tests. Do it.



Mjanaging Tjechnical Djebt – What is technical debt? – Tests. Do it.

2 6


tech-debt-talk


On Github crccheck / tech-debt-talk

Mjanaging Tjechnical Djebt

I’ve been using Django since version 1.2? My 4th DjangoCon.

I've only been a professional software developer for 6 years this is what I've seen and these are the practical things I've seen to help combat technical debt.

What is technical debt?

  • [Technical debt] can be thought of as work that needs to be done before a particular job can be considered complete or proper. [citation needed]
  • Gene Kim: Left unchecked, technical debt will ensure that the only work that gets done is unplanned work!
  • Every line of code is technical debt

EVERY LINE: Every line was written for a reason, you have to know why

NEXT: No definition in Urban Dictionary

  • When you fix one bug only to create another
  • When you can’t deliver a feature because you don’t know what will break
  • If you’re using a rapid development framework and can’t rapidly develop
  • “it works for me”
  • Everything is on fire all the time
I can laugh about these, because now that I am an expert, I never have to deal with these again :niran-laugh:
  • When you fix one bug only to create another
  • When you can’t deliver a feature or security patch because you don’t know what will break
  • If you’re using a rapid development framework and can’t rapidly develop
  • “It works for me”
  • Everything is on fire all the time

Frustration levels are high because you never deliver, never move forward

Django lets you build things very quickly. So developers will start off loving Django. But if you build without regard to technical debt, you stop being able to deliver. Django turns into a nightmare and now Django becomes the scapegoat for your organization.

Tests. Do it.

Do it! Just do it! Don't let your dreams be dreams. Yesterday, you said you would write tests tomorrow. So just do it! Make your dreams come true! Just do it!

When to test?

If you're making an assumption, you should test it.

def zipcode(s):
    """
    Get the 5 digit zipcode from any zipcode format.

    >>> zipcode('12345-6789')
    "12345"
    >>> zipcode('12345')
    "12345"
    >>> zipcode(12345)
    "12345"
    >>> zipcode(1337)
    "01337"
    >>> zipcode(None)
    ValueError: s must be a US postal zip code
    """
    return s[:5]

Don't have a single test?

Gotta start somewhere.

from django.test import TestCase

class HomeTests(TestCase):
    def test_homepage_loads(self):
        self.client.get('/')

blog.doismellburning.co.uk/2015/08/05/the-most-efficient-django-test/

When to test? (cont'd)

Consider regression testing should be mandatory.

You will sleep better at night with good test coverage.

Recurring bugs are a real embarrassment. “fool me once, shame on you. Fool me - you can't get fooled again.”

NEXT: oh yeah, one more thing. if you’re not sure if you should write a test, then...

Coverage

Coverage is just a tool to help you figure out where you're missing tests.

100% coverage isn't useful if you have crappy tests.

Signs your tests are crappy

  • Can other people read and extend them?
  • Are tests getting maintained? Or are they a source of frustration?
  • Do your tests describe the problem in a way where someone could throw away the code and use the tests as a spec?

Not only the tests as a spec, but the documentation, maybe even just the readme.

We'll revisit tests later.

Bitrot / Software entropy

 

If you're not touching something, it's getting worse

If you don't have the manpower to continously touch all your code and documentation, that's a smell

Improvement Kata - Mike Rother

Applications age like fish, data ages like wine You may have also heard about the Toyota Kata

Automation

“Any task you do at least once a month should take less time than it takes to get a cup of coffee” †

†Original: Any system that must be deployed more often than monthly does so faster than you can get a cup of coffee.

Maintaining a reproducible environment drastically reduces "it works for me".

Know what's better than having to do just one little step? Having to do zero.

Tox has multidimentional stuff now that is great and lets you try the same code on a matrix of Python and Django versions.

If you automate the routine things away, you get to do fun creative things.

xkcd/1205
xkcd/1319

Code quality

  • Practice makes perfect. Always be iterating.
  • Keep things simple. Code that requires less context to understand is more readable.
  • Keep things short. Every LOC is debt.
  • Know your audience.
  • Choose readability over performance.
  • import this

Avoid magic and cleverness. I found that whenever I write clever code, I'll naturally refactor it to be dumber.

oh, you just learned about getattr and now you want to use it everywhere? Have fun trying to figure out why things randomly don’t work.

The idea of keeping things simple isn’t to handicap yourself from using fancy language features, to avoid special snowflake patterns that are only used once and require more context to read.

Know your audience -- If you're doing scientific python, using Pandas to do a filter is fine. Importing pandas just to do a simple filter in a view would be ridiculous.

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
...
from operators import attrgetter

friends = [
    aquacorg,
    corgnelius,
    stumphery,
    gatsby,
    scout,
    tibby,
    winston,
]

# Which is better?
accounts = map(attrgetter('instagram'), friends)

accounts = [x.instagram for x in friends]

Pizza Story

Code Silos Specialist Unicorns

Avoiding code silos

Make yourself obsolete

Pretend you're training your replacement

Could you go on vacation without your laptop?

avoid silos. code review/pair programming are just the beginning of making sure code knowledge is shared.

Related topics

  • #nocomments – Do comments help or hurt readability?
  • Cyclomatic complexity – quantify readability
  • McCabe
  • PEP8
  • PyFlakes

Code review

This is the single most practical way to get better.

  • People take too much time to review
  • I'm the boss
  • We need to push this hotfix for production ASAP

Maybe you're the boss, or maybe even big boss

Spread the blame

Coding under pressure

No one to review your code?

Sleep on it

Practice with others on open source projects

As iron sharpens iron, so one person sharpens another.

Open pull requests anyways. Seeing your diff at a higher level will give you a new perspective.

Pull requests

The rule is: one pull request = one change

smaller pull requests = faster reviews and you can move on. Watch out for bike shedding tighter feedback loops

THE RULE IS: You should break this rule. Well, bend. If you see something while writing code, you should fix it as long as it doesn’t hurt the readability of the pull request. if it does, make a ticket to clean it up.

Boy scout rule

Leave this world a little better than you found it.– Robert Baden Powell

If you have to touch a test to clean something, leave it alone for now.

Do a good turn daily? Be Prepared?

Code review is more than reviewing code

  • Repeatable?
  • Documentated?
  • Maintainable? (Does the feature justify the technical debt?)

Raymond Hettinger - Beyond PEP 8

Code review is more than just checking syntax and style.

REPEATABLE EXAMPLE: Last week (August 2015) I used a utility and it wasn't working, so I went back to the pull request and saw that I was using the utility the wrong way.

DOCUMENTED EXAMPLE: If you're adding a new feature to the CMS, updating and communicating to users that there's a change is part of definition of done.

EXAMPLE: You may have decided to implement something with a custom templatetag but during code review, you decide to use a jquery one-liner.

Technical debt note

A fiscal note is a written estimate of the costs, savings, revenue gain, or revenue loss that may result from implementation of requirements in a bill or joint resolution. It serves as a tool to help legislators better understand how a bill might impact the state budget as a whole, individual agencies, and in some instances, local governments.

Depends on your organization

It's more than just stating what the LOC change is. Making sure everyone is cognizant of the debt being made.

concrete examples of hidden technical debt: Adding a snowflake pattern, adding a Vary cookie to page that did not have one before.

decreases because of deletions, looser coupling between this and higher ed, and modernized higher ed to work with newer django

decreases because whaaaaat is all this crazy logic that was there before?

increases to add an oft requested feature. there's very light test coverage.

decreases because this adds documentation and removes non .env file workflows

Pair programming

The best way to up code quality and share knowledge

 
  • tmux + vim
  • Driver + Navigator
  • Pair Programming Ping Pong Pattern

this costs the most time, but has a excellent long term benefits. Code review with even tighter feedback loop.

PING PONG: async pair programming

Teams

Guess what? Software teams are just like other teams

The team should care about controlling technical debt

Robots can help

Past you is the worst coworker ever

what about if you’re alone? well, you just have to learn through experience. working with past self is a lot like working with a terrible coworker.

When I interview now, I assume they passed technical test already. I ask cultural fit questions.

Culture

Get faster feedback loops

  • Sprints
  • Code review
  • CI/CD
  • Reduce friction
  • Release early, release often
  • Use analytics

Culture

Embrace failure (high trust)

Learn how to say “NO”

Write up concrete plans to reduce technical debt as actionable items

Task Reduce homepage query count ┬──┬ Fix inconsistent migrations ┬───┬ Add CSRF ┬─┬ Refactor Polls views to CBV ┬─┬ Upgrade from Django 1.5 to 1.8 ┬─────┬

Writing down your technical debt can be cathartic.

And look here, here's some low hanging fruit.

Trading technical debt

  • Deciding up front what you're optimizing for
  • Technical debt is a deliberate design decision

For startups, getting first to market is probably more important than building quality.

Focus on good interfaces where you can throw away code that fulfills those interfaces. (remember throw away code, keep the tests?)

Making code hard to maintain on purpose as a reminder that it should not be touched.

Technical debt is an engineering trade-off. Good Cheap Fast, pick two. You can also just pick one if you want to.

Now what?

Single responsibility principle

Do one thing, do it well

UNIX philosophy, Docker images, Microservices, OOP

Document how it works:Throw away the code, reuse the interface

Kill babies

Taco Bell Future

  • Froze content with wget
  • Old stuff still worked and looked the same
  • Deleted all the things
  • New stuff was easy to write

The TweetWire

PHP

Django Models

Cron Job

Streaming Client

No Admin

<a class="twitter-timeline" />

Django is great

Writing tests is easy

Less boilerplate

Great community

Don't customize the Admin

The admin is a developer interface, not a CMS

Writing your own seems like it's more technical debt, but you'll end up with a better user experience

Admin customizations == tight coupling

Are you writing Selenium tests for custom admin behavior? Because that's what you have to do if you start customizing the admin. Lots of intersecting code. If you need a custom interface, write it. Bits of the admin are often re-usable outside the admin (permissions, require-login). Use third party admin integrations that are maintained, don’t do it yourself. If you write something that changes, are you going to read through every Django release note? since admin things tend to be hacky, more expensive to fix/maintain in general.

A partial list of admin packages you can use instead of coding your own:

Clean as you cook

Delete code! That's what source control is for.

If you think you'll need to toggle something, don't use comments, use a feature switch.

Open tickets for things you find (see boy scout rule)

Naming things

 

Is it greppable?

Find/Replace friendly?

Name variables (especially in tests) so they read like English

Readable tests

Keep boilerplate out.

class ArticleManagerTests(TestCase):
    def test_stuff(self):
        a = Article(active=True,
                    title='Corgi Beach Day',
                    slug='corgi-beach-day',
                    text='Corgs got sand on their stumpers'
                    pub_date='2015-09-31')
        self.assertIn(a, Article.objects.active())

    def test_active_returns_only_active_articles(self):
        article = ArticleFactory(active=True)
        self.assertIn(article, Article.objects.active())
Factoryboy makes it so the attributes you specify are the ones that are relevant to the test.

Use unique names

$ ./manage.py shell_plus
# Shell Plus Model Imports
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
from tx_elevators.models import Building, Elevator
# Shell Plus Django Imports
from django.utils import timezone
from django.conf import settings
from django.core.cache import cache
from django.db.models import Avg, Count, F, Max, Min, Sum, Q, Prefetch
from django.core.urlresolvers import reverse
from django.db import transaction
>>> Elevator.objects.count()
51952
Yes, shell plus gives you ways around this, but at the cost of more technical debt.

Avoid the test client

Don’t use it! For unit tests.

Do Use it! For integration tests

urls
request middleware
views (dispatch/get/get_context_data)
response middleware
context_processor

It’s useful if you’re testing routes, headers, vary cookie.

Avoid model inheritance

Mixins and Base classes

Hurts readability and performance

Django Extensions has examples for what a good model mixin is: TimeStampedModel, TitleDescriptionModel, TitleSlugDescriptionModel

django-extensions.readthedocs.org/en/latest/model_extensions.html

Stay fresh

requirements.txt:

Every pull request, if a version can be bumped, bump it.

$ pip list --outdated
$ bundle outdated
$ npm outdated

This only works if you have test coverage!

Something I've always wanted to try is a Jenkins job that checks for updated requirements and tells me when they break.

Reading

Docker and DevOps by Gene Kim, Dockercon ’14, 38m45s Inheriting a Sloppy Codebase by Casey Kinsey, 42m04s Simple Made Easy by Rich Hickey, 61m26s Implementing a Strong Code-Review Culture 37m48s

Reading (for real)

   

Similar discussions

  • Microservices vs Monoliths
  • Not invented here syndrome
  • Feature creep
  • Insert product development buzzword here *

 

 

 
  • Get frustrated
  • Get lazy – Automate away routine tasks
  • Faster feedback loops
  • Always be touching
  • Treat fixing technical debt as a feature
  • Clean as you code
  • Foster a good coding culture
  • Single responsibility principle
  • Everything must be repeatable
  • Plan ahead for the whole lifecycle

Thanks!

github/crccheck/tech-debt-talk

docker run --rm -p 8000:1947 crccheck/tech-debt
Mjanaging Tjechnical Djebt