Clean Code Notes - Run 1
Book Clubbers: @Michael Katz (Deactivated), @Christopher Lee (Deactivated), @Cliff Dyer (Deactivated), @akivaR (Deactivated), @Andy Armstrong (Deactivated), @ChristinaR (Deactivated), @Eric Fischer (Deactivated), @Robert Raposa, @Renzo Lucioni (Deactivated), @ClintonB (Deactivated), @Adam Palay (Deactivated), @Nimisha Asthagiri (Deactivated)
Jan 6, 2016
Thoughts on organizing the book club
Case study of actual edX code
Go through each chapter and decide on what applies to edX
Overall thoughts on the book - writing, content, approach
Book Thoughts
We like that he acknowledges that there are multiple perspectives and multiple schools of thought
We like that he takes something complicated and makes it simpler
Chapter 3: Functions
Does smaller (and more) functions actually make it more readable?
In python
By the way, function calls don't actually get optimized away.
Performance issues should be exceptional cases - and should be commented as such.
Is Python's coding practice counter to these rules?
As a team that does a lot of code reviews, code readability is very useful.
Functions should only do 1 thing
Triad rule of number of parameters?
Python does have keyword arguments that helps mitigate this.
But JS doesn't.
But too many arguments means more combinations for testing
And also, we tend to have arguments that are mutually exclusive of another.
We may want to still flag this situation and consider why.
Step-down rule
Boy Scout rule
Clean up while updating code.
This is very difficult to do because it takes us down a long path.
How to encourage and make this happen at edX?
Be stricter in code reviews.
Have separate commits for refactoring.
Sometimes it's difficult because of merge conflicts.
Ways of deprecating - come back to it.
Duck-typing in Python?
Example: Argument can be a String or an Object.
PyContracts
Functions that are both getters-and-setters
Side Effects
Error Handling
all error handling in a separate function? really?
case study: views.py versus api.py separation of concerns.
api.py really needed?
If there are no in-process requirement for it, then just exclude it?
If people start using api.py and we want to separate into an IDA, it makes it difficult.
If we don't create an api.py initially, we can create it later when it's actually required.
For an app with many views, you can use a views folder with multiple modules.
edX Takeaways
Is there somewhere we can point to for best practices?
Jan 13, 2016
Chapter 4: Comments
Comments do get out of date. So commenting on things that aren't obvious makes sense.
Do we have an obligation when it comes to comments because of the open source nature of the platform
API Docs
pylint - docstring requirements
explicit disabling locally versus updating pylint to no longer require docstrings
but if explicitly disabled locally, that can also get stale
Why do docstrings have to be multi-line?
Takes up a lot of vertical space
Rationale: Code diffs are clearer? (similar to the trailing comma guideline)
Petition against @Ned Batchelder (Deactivated)?
Coding convention: triple double quotes
Ambiguity of parameters
"Could be a string or a list"
Duck typing
JSON is mildly typed
API bindings
DRF-Swagger will automatically create docstrings
Now that DRF is upgraded, we can look into this.
@ClintonB (Deactivated) will send a link to how they do this for Course Discovery
Can create common DRF Serializer fields - for example, for OpaqueKeys.
TODO comments
Have corresponding JIRA tickets
Distinguish between
short-term TODOs (address before merging the PR)
long-term TODOs (with JIRA tickets)
TODOs for code improvements - may not need tickets
lint-checker for TODO to contain ticket numbers
When to use comments and when not to seemed conflicted
All comments can get stale
So only write comments that are really necessary
In place of comments
use intermediate, well named, local variables
for conditionals
for long lines
But when there are local variables, you expect it to be used multiple times.
better method names
List comprehensions that are 5 lines should probably be for loops instead - for readability.
The python ternary operator reads much better since it's in one line.
But some developers don't like them as much.
"x or y" OR "x if x else y"
Chapter 5: Formatting
Python is very explicit
Dependent functions
In test files, we've generally been putting the helper functions on top (which is reverse from his recommendations)
But for other classes, we generally use the top-down approach.
For xCode
File hierarchy on disk is different from hierarchy in the IDE
So needs to be manually updated
Agree: Extra whitespace brings attention to wrong things
Like: Breaking indentation always returning back to the indented form
Agree: If statements not having braces
In JS, variable declarations always on top because of scoping rules, but is it helpful for readability?
Jan 20, 2016
Chapter 6: Objects and Data Structures
Law of Demeter
"objects that you created" is Java-specific
distinguishing between objects that you've created and those that you depend on
abstraction layers
in some places, it says no more than one period.
Train wrecks
distinguishing between breaking abstractions through objects, and
accessing data models directly
Objects versus Data Structures
Our django models have become hybrids
What's the right pattern of having separation of concerns between persistence and the object-level abstraction?
Should we create a new business logic layer on top of our models?
Too many layers is not advantageous: API view -> Utils function -> .... eventually a Model
Trade-off between making use of the framework and having abstraction layers
If we think about the edx-platform codebase history, it seems we add more behaviors overtime, but no data structures.
This implies perhaps we should think more procedural, rather than OO.
Chapter 7: Error Handling
Using exceptions rather than error codes
No brainer.
Throwing exceptions instead of returning None
We do this in our codebase.
It's fine as long as we document it - we don't have a result for you.
We do like his suggestion of having a special case object.
Using django framework conventions
ObjectDoesNotExist exception
get_or_none - returns None
first - return None
In Java, exceptions are expensive, so they try to avoid them.
But in Python, that's not the case.
For example, getattr, just catches the exception and returns False/True.
But if catching the exception is acceptable for code, then go fo it.
By returning error codes, the callee can be assured that the caller will handle all cases.
But then too many if statements.
And probably end up wrapping it anyway, and throw an exception.
If you make a mistake, and completely miss a case, then would get unnoticed.
In some other languages, however, you have to pull out your object explicitly.
hasattr - in case the object raises an exception other than AttributeError, later version of Python now catches it and returns False. Before, the exception was passed through.
API wrapper mixin makes it easy to translate View exceptions to formatted HTTP error return objects.
Some languages have @Nullable decorators to explicitly specify which functions have chosen to return Null.
Passing Null around
In some cases, we do want to do this - for example, Null values in Address fields, as opposed to empty strings.
One way of thinking about this for Python: passing None for optional parameters is fine, but for non-optional parameters should be seriously vetted.
"?." is not yet accepted into Python.
Next time: Chapters 8 and 9.
Jan 27, 2016
Chapter 8 and 9
Cliff discovered a useful tool! Mocking server for IDAs that need to talk to each other.
There's also a python tool called beta-max which "caches" IDA responses (so you can write tests against responses)
Boundaries
Wrapping 3rd party libraries: it can easily be overdone.
Forking, and making minimal changes, can bite us, since it makes it more complicated to upgrade the library
Could be a good place to get in the habit of wrapping
Coverage of 3rd-party library — does it matter if it has low-percentage test coverage?
If we wrapped 3rd-party libraries, having unit tests for the wrapper could prevent against 3rd-party libraries of unknown quality
Difference between testing against 3rd-party libraries and services
Testing
"What we talk about when we talk about testing" — "I'm sure there's a blog post called that out there"
TDD
Clean code: don't write more code than is sufficient to pass the test
Relationship to red/green/refactor? — Similar, when you get down to first principles
Sometimes DRY-ness in tests make them harder to understand (DRY = don't repeat yourself)
DRY tests make it easier to do large refactoring
DSLs for Testing
Course DSL for creating courses in tests
Using named-tuples for ddt --> makes tests more readable (could also use a dict)
Feb 10, 2016
Chapter 10: Classes
Small classes?
Need better developer tools
It would be great if our IDEs had a better visual tool for presenting relationships between our classes - so it's easier to understand how all the small classes fit together.
EComm used django-oscar, which had a ton of classes - but made it hard to figure out where to start.
Hard to discover classes, but easier to add new classes.
Single responsibility principle
What is a responsibility?
Underlying principles though help
make complex code easier to understand.
make it easier to test.
Can go overboard on generalizing everything.
Are the SQL classes in table 10-10 really better than the original code in 10-9?
Description of the class without using if, or, and, but.
Cohesion as a useful tool to gauge a class - as a guideline.
Chapter 11: Systems
Dependency Injection
decoupling, extension mechanism
iOS attempted this for string resolution, but instead of passing the dependency through all layers, created a singleton as a lookup service.
Enterprise Java Beans
property setters/getters decoupled from the storage layer and the behavior
Aspects
Declare in a separate place all the before and after operations to do for its decorated methods.
Separated Code dependency (code depending code implemented elsewhere)
Aspects
Python Mixins
Python Decorators
DRF is a good example for us to look at.
Design exercise: What design patterns to use when designing an access control permission checking framework?
Feb 17, 2016
Chapter 12: Emergence
Not controversial to keep the code clean
This chapter didn't introduce any new ideas
Code that doesn't have tests/hasn't been tested, should not be shipped.
Keep minimal classes AND keep them short?
The idea is that if the code is clean, classes will be minimal and short.
Short could also be interpreted as conceptually short instead by lines.
In edx, seems like duplication can make the code easier to read.
In test code, code duplication could make the test much easier to read.
There are some testing frameworks that use real sentences to describe tests instead of code. EdX used to use Lettuce, which did this.
Pylint complains about test function name length. This limit has been tweaked up. Possibly increase this?
Englishy test frameworks might be better for UI testing.
Chapter 13: Concurrency
Ned made a great concurrency joke
Concurrency is hard.
We believe edx is single threaded, but has multiple processes.
Mongo and django, have their own mechanisms for concurrency.
GIL! Global Interpreter Lock
Python code that runs on two threads, are still not run at the same time and on a single core
Ssssssssss
March 9, 2016
Chapter 17: Smells and Heuristics
Removing commented out code - Yes or No? YES!!
G30: Functions should do one thing
Creating a Coding Standard Convention Wiki
Find terminology used throughout the book that we want to use to identify.
Make sure we don't get too jargony - that it makes it hard for newcomers to come up to speed.
Make sure PR reviewers still continue to explain themselves rather than just throw out a term.
Hyperlinkable sections.
Create a confluence wiki - contributor guidelines.
Action Items:
Order "Effective Python" @Cliff Dyer (Deactivated)
Invite all of Eng to club @Cliff Dyer (Deactivated)
Ping IT on creating Github team @Christopher Lee (Deactivated)
Create wiki for coding convention @Nimisha Asthagiri (Deactivated)