On Github crccheck / tech-debt-talk
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.
EVERY LINE: Every line was written for a reason, you have to know why
NEXT: No definition in Urban Dictionary
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.
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!
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]
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/
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 is just a tool to help you figure out where you're missing tests.
100% coverage isn't useful if you have crappy tests.
Not only the tests as a spec, but the documentation, maybe even just the readme.
We'll revisit tests later.
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 KataMaintaining 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.
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]
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.
This is the single most practical way to get better.
Maybe you're the boss, or maybe even big boss
Spread the blame
Coding under pressure
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.
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.
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 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.
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
The best way to up code quality and share knowledge
this costs the most time, but has a excellent long term benefits. Code review with even tighter feedback loop.
PING PONG: async pair programming
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.
Get faster feedback loops
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.
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.
Single responsibility principle
Do one thing, do it wellUNIX philosophy, Docker images, Microservices, OOP
Document how it works:Throw away the code, reuse the interface
Kill babies
PHP
Django Models
Cron Job
Streaming Client
No Admin
<a class="twitter-timeline" />
Writing tests is easy
Less boilerplate
Great community
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:
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)Is it greppable?
Find/Replace friendly?
Name variables (especially in tests) so they read like English
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.
$ ./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() 51952Yes, shell plus gives you ways around this, but at the cost of more technical debt.
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.
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
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.
github/crccheck/tech-debt-talk
docker run --rm -p 8000:1947 crccheck/tech-debt