Here are some tips I use to provide better naming for functions I write.
save_user_to_mongo
is a nice name;save_user
is not. The same thing goes withload_categories_from_cache
andload_categories
.
If you have multiple data storages, a function call doesn’t tell you which one you are using. You have to ctrl-click it. Every. Damn. Time.
Say we are writing a unit test to a function calledvalidate_user_form
. Here are some bad test names:test_validation_ok
,test_user_can_submit_profile_data
,test_test_test
,test_asdfqerfjeghmnsa
. And these names look nice and informative:test_validate_user_form_raises_no_errors_on_correct_data
,test_validate_user_form_raises_an_error_on_empty_email
,test_validate_user_form_raises_an_error_on_existing_phone
.
Important note on the behaviour part: it should say what exactly we are testing. I used to use “works_fine” and “is_ok” as a behaviour part, and it sucks: when a test fails, I had to go to the test and check what exactly is going on. Now when a test suite tells me if I broke some test, I go straight to the function it tests with all info I need to fix a problem.
These names can be quite long some times, but the use case for the names is quite different: we are not using the names in our code, but we see them in broken CI reports. It’s okay to have logs names there if they allow us to skip the “wtf is going on in the test” part.
process_and_save_profile_info
is quite readable but still quite bad. Not because of its name, but because of its nature: it should beclean_profile_info
andsave_profile_info_to_db
.
This rule is related to the single-responsibility principle and command-query separation pattern. Don’t make Bertrand Meyer angry.
list_item_to_marketplace(marketplace, item)
is a nice name, butlist_item_to(marketplace, item)
is better.
Here is an example call of a first option:list_item_to_marketplace(Marketplace.eBay, user_item)
. Word “marketplace” is repeated! Even if it looks likelist_item_to_marketplace(eBay, user_item)
it will be clumsy: we all know that eBay is a marketplace. In contrast,list_item_to(eBay, user_item)
is great.
This rule allows us to make a function name a little shorter. Such names often end up with a preposition:_at
,_to
,_in
and so on.
Sometimes you should duplicate data from the module path, and sometimes you should not.
Say you haveparsers.py
with a lot of parsers in it. Should you call parsersparse_integer
andparse_string
orinteger
andstring
? It depends on how you will use it.
You could use them in one place:parsers=[integer, string]
, then short names are okay.
You can use them directly inside business logic:age = integer(raw_data[‘user’][‘age’])
, then you might want to add the “parse” part, so the line looks more readable. But even in this case, you could stick withinteger
if you change a rule for imports of the parsers. Look at this:age = parsers.integer(raw_data[‘user’][‘age’])
. This gives us a lot of information, but it is not very English-like in my taste.
So, think of how and when you will use the function. Sometimes it is worth repeating a verb (or some other detail) from the module name.
save_full_user_profile_to_postgresql_storage_sync
can be easily shorted tosave_full_profile_data_to_postgresql_sync
since we all know that PostgreSQL is storage. If all functions in your project are synchronous, you probably should remove_sync
part. If you don’t have “short” or “partial” user profiles, you should make itsave_user_profile_to_postgresql
. And finally, you might want to replacepostgresql
withdb
(if Postgres is the only database storage you’re using in the project). So we made a long path fromsave_full_user_profile_to_postgresql_storage_sync
tosave_user_profile_to_db
. The new name is much shorter, but it keeps the same context. Magic!
The transformation depends on your project. If you have sync and async fetchers, use Postgresql and Mysql as storages, use Postgres as data storage and as cache storage, you should stick with the initial name. But you most likely don’t.
So, give the names of your functions a meaningful context and think of length optimization.
Say you’re writing a website about books publishing. You most likely use these words in your names: book, publisher, edition, typography, revision, impression, remainders and so on.
Make sure this vocabulary is shared between team members, and everyone is using it. If you’re using the term “typography”, then using “printing gallery” is an error.
A good thing to do is write the vocabulary down with meanings of all area-specifics one can find in the project.
I hate typos. Hate them with me.
Set up an automation that will check for typos in names inside your code. If a typo is found, it breaks the build.
I usescspell for that thing. It takes some work to set the thing up and collect initial vocabulary, but now it works like a charm.
Another profit of tools like scspell is that they make you collect all specific words you use in a single place. Check the previous item of the article. It plays well together with vocabulary files.
are_user_active
— looks weird, right? It looks fine once you’ve changed it tois_user_active
.
Usually, function names use basic English grammar: is/are, have/have, present/past, simple/perfect. It won’t take you long to fix errors like that, but it increases readability.
When writing a function, think about how and where the function will be called. Will the name fits there? Will it be readable?
I was trying to collect tricks to make it readable in any case, but don’t do the Cargo cult around the thing.
A good name for a function should be readable in two places: where the function is defined and called.
You won’t miss the definition since you’re writing it when writing a function name.
All you need is to think about a second case: will the name look readable when the function is called.