Run prettier everywhere

This commit is contained in:
Charles-Axel Dein 2020-07-21 09:14:24 +02:00
parent 6f941a8ab3
commit c328d43192
13 changed files with 743 additions and 785 deletions

1063
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Antipatterns](#antipatterns)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Antipatterns
============
# Antipatterns
This is a list of antipatterns.
* [Code antipatterns](./code-antipatterns.md)
* [Python antipatterns](./python-antipatterns.md)
* [SQLAlchemy antipatterns](./sqlalchemy-antipatterns.md)
* [Error handling antipatterns](./error-handling-antipatterns.md)
* [Tests antipatterns](./tests-antipatterns.md)
- [Code antipatterns](./code-antipatterns.md)
- [Python antipatterns](./python-antipatterns.md)
- [SQLAlchemy antipatterns](./sqlalchemy-antipatterns.md)
- [Error handling antipatterns](./error-handling-antipatterns.md)
- [Tests antipatterns](./tests-antipatterns.md)

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Antipatterns](#antipatterns)
@ -25,8 +26,7 @@
Most of those are antipatterns in the Python programming language, but some of
them might be more generic.
Strict email validation
-----------------------
## Strict email validation
It is almost impossible to strictly validate an email. Even if you were writing
or using a regex that follows
@ -41,8 +41,7 @@ To sum up, don't waste your time trying to validate an email if you don't need
to (or just check that there's a `@` in it). If you need to, send an email with
a token and validate that the user received it.
Late returns
------------
## Late returns
Returning early reduces cognitive overhead, and improve readability by killing
indentation levels.
@ -66,8 +65,7 @@ def toast(bread):
toaster.toast(bread)
```
Hacks comment
-------------
## Hacks comment
Bad:
@ -78,13 +76,13 @@ toaster.restart()
There's multiple things wrong with this comment:
* Even if it is actually a hack, no need to say it in a comment. It lowers the
- Even if it is actually a hack, no need to say it in a comment. It lowers the
perceived quality of a codebase and impacts developer motivation.
* Putting the author and the date is totally useless when using source control
- Putting the author and the date is totally useless when using source control
(`git blame`).
* This does not explain why it's temporary.
* It's impossible to easily grep for temporary fixes.
* [Louis de Funès](https://en.wikipedia.org/wiki/Louis_de_Fun%C3%A8s) would never
- This does not explain why it's temporary.
- It's impossible to easily grep for temporary fixes.
- [Louis de Funès](https://en.wikipedia.org/wiki/Louis_de_Fun%C3%A8s) would never
write a hack.
Good:
@ -95,10 +93,10 @@ Good:
toaster.restart()
```
* This clearly explains the nature of the temporary fix.
* Using `TODO` is an ubiquitous pattern that allows easy grepping and plays
- This clearly explains the nature of the temporary fix.
- Using `TODO` is an ubiquitous pattern that allows easy grepping and plays
nice with most text editors.
* The perceived quality of this temporary fix is much higher.
- The perceived quality of this temporary fix is much higher.
## Repeating arguments in function name
@ -146,8 +144,7 @@ Which produces much more concise code:
toaster = Toasters.get(1)
```
Repeating function name in docstring
------------------------------------
## Repeating function name in docstring
Bad:
@ -159,8 +156,8 @@ def test_return_true_if_toast_is_valid():
Why is it bad?
* The docstring and function name are not DRY.
* There's no actual explanation of what valid means.
- The docstring and function name are not DRY.
- There's no actual explanation of what valid means.
Good:
@ -177,8 +174,7 @@ def test_brioche_are_valid_toast():
assert is_valid(Toast('brioche')) is true
```
Unreadable response construction
--------------------------------
## Unreadable response construction
TODO
@ -207,8 +203,7 @@ def get_data():
}
```
Undeterministic tests
---------------------
## Undeterministic tests
When testing function that don't behave deterministically, it can be tempting
to run them multiple time and average their results.
@ -235,11 +230,11 @@ def test_function():
There are multiple things that are wrong with this approach:
* This is a flaky test. Theoretically, this test could still fail.
* This example is simple enough, but `function` might be doing some
- This is a flaky test. Theoretically, this test could still fail.
- This example is simple enough, but `function` might be doing some
computationally expensive task, which would make this test severely
inefficient.
* The test is quite difficult to understand.
- The test is quite difficult to understand.
Good:
@ -252,8 +247,7 @@ def test_function(mock_random):
This is a deterministic test that clearly tells what's going on.
Unbalanced boilerplate
----------------------
## Unbalanced boilerplate
One thing to strive for in libraries is have as little boilerplate as possible,
but not less.
@ -269,8 +263,7 @@ library.
I think Flask and SQLAlchemy do a very good job at keeping this under control.
Inconsistent use of verbs in functions
--------------------------------------
## Inconsistent use of verbs in functions
Bad:
@ -299,9 +292,9 @@ of toasts in the third function.
This is based on personal taste but I have the following rule:
* `get` prefixes function that return at most one object (they either return
- `get` prefixes function that return at most one object (they either return
none or raise an exception depending on the cases)
* `find` prefixes function that return a possibly empty list (or iterable) of
- `find` prefixes function that return a possibly empty list (or iterable) of
objects.
Good:
@ -324,8 +317,7 @@ def find_toasts(color):
return filter(lambda toast: toast.color == color, TOASTS)
```
Opaque function arguments
-------------------------
## Opaque function arguments
A few variants of what I consider code that is difficult to debug:
@ -348,12 +340,11 @@ def create2(*args, **kwargs):
Why is this bad?
* It's really easy to make a mistake, especially in interpreted languages such
as Python. For instance, if I call `create({'name': 'hello', 'ccolor':
'blue'})`, I won't get any error, but the color won't be the one I expect.
* It increases cognitive load, as I have to understand where the object is
- It's really easy to make a mistake, especially in interpreted languages such
as Python. For instance, if I call `create({'name': 'hello', 'ccolor': 'blue'})`, I won't get any error, but the color won't be the one I expect.
- It increases cognitive load, as I have to understand where the object is
coming from to introspect its content.
* It makes the job of static analyzer harder or impossible.
- It makes the job of static analyzer harder or impossible.
Granted, this pattern is sometimes required (for instance when the number of
params is too large, or when dealing with pure data).
@ -365,8 +356,7 @@ def create(name, color='red'):
pass # ...
```
Hiding formatting
-----------------
## Hiding formatting
Bad:
@ -401,8 +391,8 @@ def get_user(user_id):
Even if you were duplicating the logic once or twice it might still be fine, because:
* You're unlikely to re-use anywhere else outside this file.
* Putting this inline makes it easier for follow the flow. Code is written to be read primarily by computers.
- You're unlikely to re-use anywhere else outside this file.
- Putting this inline makes it easier for follow the flow. Code is written to be read primarily by computers.
## Returning nothing instead of raising NotFound exception
@ -456,8 +446,8 @@ def do_stuff_b(toaster):
What's the correct things to do?
* If you expect the object to be there, make sure to raise if you don't find it.
* If you're using SQLAlchemy, use `one()` to force raising an exception if the object can't be found. Don't use `first` or `one_or_none()`.
- If you expect the object to be there, make sure to raise if you don't find it.
- If you're using SQLAlchemy, use `one()` to force raising an exception if the object can't be found. Don't use `first` or `one_or_none()`.
## Having a library that contains all utils
@ -476,6 +466,6 @@ def upload_to_sftp(...):
...
```
`util` or `tools` or `lib` modules that contain all sorts of utilities have a tendency to become bloated and unmaintainable. Prefer to have small, dedicated files.
`util` or `tools` or `lib` modules that contain all sorts of utilities have a tendency to become bloated and unmaintainable. Prefer to have small, dedicated files.
This will keep your imports logical (`lib.date_utils`, `lib.csv_utils`, `lib.sftp`), make it easier for the reader to identify all the utilities around a specific topic, and test files easy to keep organized.
This will keep your imports logical (`lib.date_utils`, `lib.csv_utils`, `lib.sftp`), make it easier for the reader to identify all the utilities around a specific topic, and test files easy to keep organized.

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Database anti-patterns](#database-anti-patterns)
@ -7,11 +8,9 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Database anti-patterns
======================
# Database anti-patterns
Using `VARCHAR` instead of `TEXT` (PostgreSQL)
----------------------------------------------
## Using `VARCHAR` instead of `TEXT` (PostgreSQL)
Unless you absolutely restrict the width of a text column for data consistency
reason, don't do it.
@ -22,9 +21,9 @@ shows that there's fundamentally no difference in performance between
`char(n)`, `varchar(n)`, `varchar` and `text`. Here's why you should pick
`text`:
* `char(n)`: takes more space than necessary when dealing with values shorter
- `char(n)`: takes more space than necessary when dealing with values shorter
than n.
* `varchar(n)`: it's difficult to change the width.
* `varchar` is just like `text`.
* `text` does not have the width problem that `char(n)` and `varchar(n)` and
- `varchar(n)`: it's difficult to change the width.
- `varchar` is just like `text`.
- `text` does not have the width problem that `char(n)` and `varchar(n)` and
has a cleaner name than `varchar`.

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Error handling anti-patterns](#error-handling-anti-patterns)
@ -12,8 +13,7 @@
# Error handling anti-patterns
Hiding exceptions
-----------------
## Hiding exceptions
There are multiple variations of this anti-pattern:
@ -40,9 +40,9 @@ def toast(bread):
It depends on the context but in most cases this is a bad pattern:
* **Debugging** those silent errors will be really difficult, because they won't show up in logs and exception reporting tool such as Sentry. Say you have an undefined variable in `Toaster.insert()`: it will raise `NameError`, which will be caught, and ignored, and you will never know about this developer error.
* **The user experience** will randomly degrade without anybody knowing about it, including the user.
* **Identifying** those errors will be impossible. Say `do_stuff` does an HTTP request to another service, and that service starts misbehaving. There won't be any exception, any metric that will let you identify it.
- **Debugging** those silent errors will be really difficult, because they won't show up in logs and exception reporting tool such as Sentry. Say you have an undefined variable in `Toaster.insert()`: it will raise `NameError`, which will be caught, and ignored, and you will never know about this developer error.
- **The user experience** will randomly degrade without anybody knowing about it, including the user.
- **Identifying** those errors will be impossible. Say `do_stuff` does an HTTP request to another service, and that service starts misbehaving. There won't be any exception, any metric that will let you identify it.
An article even named this [the most diabolical Python antipattern](https://realpython.com/blog/python/the-most-diabolical-python-antipattern/).
@ -84,14 +84,14 @@ __main__.ToastException: Could not toast bread
Sometime it's tempting to think that graceful degradation is about silencing
exception. It's not.
* Graceful degradation needs to happen at the **highest level** of the code, so that the user can get a very explicit error message (e.g. "we're having issues with X, please retry in a moment"). That requires knowing that there was an error, which you can't tell if you're silencing the exception.
* You need to know when graceful degradation happens. You also need to be
- Graceful degradation needs to happen at the **highest level** of the code, so that the user can get a very explicit error message (e.g. "we're having issues with X, please retry in a moment"). That requires knowing that there was an error, which you can't tell if you're silencing the exception.
- You need to know when graceful degradation happens. You also need to be
alerted if it happens too often. This requires adding monitoring (using
something like statsd) and logging (Python's `logger.exception` automatically
adds the exception stacktrace to the log message for instance). Silencing an
exception won't make the error go away: all things being equal, it's better
for something to break hard, than for an error to be silenced.
* It is tempting to confound silencing the exception and fixing the exception. Say you're getting sporadic timeouts from a service. You might thing: let's ignore those timeouts and just do something else, like return an empty response. But this is very different from (1) actually finding the root cause for those timeouts (e.g. maybe a specific edge cases impacting certain objects) (2) doing proper graceful degradation (e.g. asking users to retry later because the request failed).
- It is tempting to confound silencing the exception and fixing the exception. Say you're getting sporadic timeouts from a service. You might thing: let's ignore those timeouts and just do something else, like return an empty response. But this is very different from (1) actually finding the root cause for those timeouts (e.g. maybe a specific edge cases impacting certain objects) (2) doing proper graceful degradation (e.g. asking users to retry later because the request failed).
In other words, ask yourself: would it be a problem if every single action was failing? If you're silencing the error, how would you know it's happening for every single action?
@ -171,8 +171,7 @@ def main():
toast('brioche')
```
Raising unrelated/unspecific exception
--------------------------------------
## Raising unrelated/unspecific exception
Most languages have predefined exceptions, including Python. It is important to make sure that the right exception is raised from a semantic standpoint.
@ -201,7 +200,6 @@ def validate(toast):
`TypeError` is here perfectly meaningful, and clearly convey the context around the error.
## Unconstrained defensive programming
While defensive programming can be a very good technique to make the code more resilient, it can seriously backfire when misused. This is a very similar anti-pattern to carelessly silencing exceptions (see about this anti-pattern in this document).
@ -219,13 +217,12 @@ def get_user_name(user_id):
While this may look like a very good example of defensive programming (we're returning `unknown` when we can't find the user), this can have terrible repercussions, very similar to the one we have when doing an unrestricted bare `try... except`:
* A new developer might not know about this magical convention, and assume that `get_user_name` is guaranteed to return a true user name.
* The external service that we're getting user name from might start failing, and returning 404. We would silently return 'unknown' as a user name for all users, which could have terrible repercussions.
- A new developer might not know about this magical convention, and assume that `get_user_name` is guaranteed to return a true user name.
- The external service that we're getting user name from might start failing, and returning 404. We would silently return 'unknown' as a user name for all users, which could have terrible repercussions.
A much cleaner way is to raise an exception on 404, and let the caller decide how it wants to handle users that are not found.
Unnecessarily catching and re-raising exceptions
------------------------------------------------
## Unnecessarily catching and re-raising exceptions
Bad:
@ -299,7 +296,7 @@ Another problem with this pattern is that you can consider it quite useless to d
> Error handling, and recovery are best done at the outer layers of your code base. This is known as the end-to-end principle. The end-to-end principle argues that it is easier to handle failure at the far ends of a connection than anywhere in the middle. If you have any handling inside, you still have to do the final top level check. If every layer atop must handle errors, so why bother handling them on the inside?
*[Write code that is easy to delete, not easy to extend](http://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to)*
_[Write code that is easy to delete, not easy to extend](http://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to)_
A better way:
@ -328,4 +325,4 @@ def call_3():
More resources:
* [Hiding exceptions](https://github.com/charlax/antipatterns/blob/master/code-antipatterns.md#hiding-exceptions)) anti-pattern.
- [Hiding exceptions](https://github.com/charlax/antipatterns/blob/master/code-antipatterns.md#hiding-exceptions)) anti-pattern.

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [MVCS Antipatterns](#mvcs-antipatterns)
@ -7,15 +8,13 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
MVCS Antipatterns
=================
# MVCS Antipatterns
In simple terms, Model-View-Controller-Services add a few more layers to the
MVC pattern. The main one is the service, which owns all the core business
logic and manipulate the repository layer.
Creating entities for association tables
----------------------------------------
## Creating entities for association tables
You'll often need association tables, for instance to set up a many to many
relationships between users and their toasters. Let's assume that a toaster can
@ -51,18 +50,18 @@ Heart of Software. Pearson Education. Kindle Edition.
Entities should model business processes, not persistence details
([source](http://blog.sapiensworks.com/post/2013/05/13/7-Biggest-Pitfalls-When-Doing-Domain-Driven-Design.aspx/)).
* In that case, `UserToaster` does not map to anything the business is using.
- In that case, `UserToaster` does not map to anything the business is using.
Using plain English, somebody might ask about "what toasters does user
A owns?" or "who owns toaster B and since when?" Nobody would ask "give me
the UserToaster for user A".
* The association table can be considered an implementation detail that should
- The association table can be considered an implementation detail that should
not (in most cases) leak in the domain layer. All the code should be dealing
with the simpler logic of "user having toasters", not UserToaster objects
being an association between a user and a toaster. This makes the code more
intuitive and natural.
* It will be easier to handle serializing a "user having toasters" than
- It will be easier to handle serializing a "user having toasters" than
serializing UserToaster association.
* This will make it very easy to force the calling site to take care of some
- This will make it very easy to force the calling site to take care of some
business logic. For instance, you might be able to get all `UserToaster`, and
then filter on whether they were bought. You might be tempted to do that by
going through the `UserToaster` object and filtering those that have
@ -72,11 +71,11 @@ Entities should model business processes, not persistence details
So in that case, I would recommend doing the following:
* Create a `User` and `Toaster` entity.
* Put the association properties on the entity that makes sense, for instance
- Create a `User` and `Toaster` entity.
- Put the association properties on the entity that makes sense, for instance
`owned_since` would be on `Toaster`, even though in the database it's stored
on the association table.
* If filtering on association properties is required, put this logic in
- If filtering on association properties is required, put this logic in
repositories. In plain English, you would for instance ask "give all the
toasters user A owned in December?", you wouldn't ask "give be all the
UserToaster for owned by user A in December".
@ -109,7 +108,7 @@ unidirectional user->toasters.
Sources:
* [7 Biggest Pitfalls When Doing Domain Driven
- [7 Biggest Pitfalls When Doing Domain Driven
Design](http://blog.sapiensworks.com/post/2013/05/13/7-Biggest-Pitfalls-When-Doing-Domain-Driven-Design.aspx/)
* [Domain-Driven Design: Tackling Complexity in the Heart of
- [Domain-Driven Design: Tackling Complexity in the Heart of
Software](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Python Antipatterns](#python-antipatterns)
@ -16,11 +17,9 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Python Antipatterns
===================
# Python Antipatterns
Redundant type checking
-----------------------
## Redundant type checking
Bad:
@ -33,8 +32,7 @@ def toast(bread):
```
In this case, checking against `None` is totally useless because in the next
line, `bread.is_toastable` would raise `AttributeError: 'NoneType' object has
no attribute 'is_toastable'`. This is not a general rule, but in this case
line, `bread.is_toastable` would raise `AttributeError: 'NoneType' object has no attribute 'is_toastable'`. This is not a general rule, but in this case
I would definitely argue that adding the type checks hurts readability and adds
very little value to the function.
@ -46,15 +44,14 @@ def toast(bread):
toaster.toast(bread)
```
Restricting version in setup.py dependencies
--------------------------------------------
## Restricting version in setup.py dependencies
Read those articles first:
* [setup.py vs.
- [setup.py vs.
requirements.txt](https://caremad.io/2013/07/setup-vs-requirement/)
* [Pin Your Packages](http://nvie.com/posts/pin-your-packages/)
* [Better Package Management](http://nvie.com/posts/better-package-management/)
- [Pin Your Packages](http://nvie.com/posts/pin-your-packages/)
- [Better Package Management](http://nvie.com/posts/better-package-management/)
**Summary: The main point is that `setup.py` should not specify explicit version
requirements (good: `flask`, bad: `flask==1.1.1`).**
@ -65,9 +62,9 @@ them both in application `app`.
Yet in 99.999% of the cases, you don't need a specific version of flask, so:
* `lib1` should just require `flask` in `setup.py` (no version specified, not
- `lib1` should just require `flask` in `setup.py` (no version specified, not
even with inequality operators: `flask<=2` is bad for instance)
* `lib2` should just require `flask` in `setup.py` (same)
- `lib2` should just require `flask` in `setup.py` (same)
`app` will be happy using `lib1` and `lib2` with whatever version of `flask` it
wants.
@ -77,8 +74,7 @@ strictly pinning (`==`) every single dependency. This way the app's stability
will be very predictable, because always the same packages version will be
installed.
Usually apps only use `requirements.txt`, not `setup.py`, because `pip install
-r requirements.txt` is used when deploying.
Usually apps only use `requirements.txt`, not `setup.py`, because `pip install -r requirements.txt` is used when deploying.
The only exception for pinning a dependency in a library is in case of a known
incompatibility, but again this should be a very temporary move, because that
@ -87,8 +83,7 @@ will prevent people from upgrading.
Ruby has a pretty similar dichotomy with [Gemspec and
gemfile](http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/).
Unwieldy if... else instead of dict
-----------------------------------
## Unwieldy if... else instead of dict
Bad:
@ -147,31 +142,26 @@ def get_operator(value):
raise ValueError('Unknown operator %s' % value)
```
Overreliance on kwargs
----------------------
## Overreliance on kwargs
TODO
Overreliance on list/dict comprehensions
----------------------------------------
## Overreliance on list/dict comprehensions
TODO
Mutable default arguments
-------------------------
## Mutable default arguments
TODO
Using `is` to compare objects
-----------------------------
## Using `is` to compare objects
TODO
[Why you should almost never use “is” in
Python](http://blog.lerner.co.il/why-you-should-almost-never-use-is-in-python/)
Instantiating exception with a dict
-----------------------------------
## Instantiating exception with a dict
Example:
@ -246,14 +236,14 @@ The proper way to update a package and its dependency is to use another tool, fo
**Reference**
* [Pin Your Packages](http://nvie.com/posts/pin-your-packages/)
- [Pin Your Packages](http://nvie.com/posts/pin-your-packages/)
# Reference
* [Pythonic Pitfalls](http://nafiulis.me/potential-pythonic-pitfalls.html)
* [Python Patterns](https://github.com/faif/python-patterns)
* [The Little Book of Python
- [Pythonic Pitfalls](http://nafiulis.me/potential-pythonic-pitfalls.html)
- [Python Patterns](https://github.com/faif/python-patterns)
- [The Little Book of Python
Anti-Patterns](http://docs.quantifiedcode.com/python-anti-patterns/)
* [How to make mistakes in
- [How to make mistakes in
Python](http://www.oreilly.com/programming/free/files/how-to-make-mistakes-in-python.pdf)
G
G

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Not paging a database query](#not-paging-a-database-query)
@ -7,12 +8,10 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Not paging a database query
---------------------------
## Not paging a database query
TODO
Returning whole objects where primary key would suffice
-------------------------------------------------------
## Returning whole objects where primary key would suffice
TODO

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [SQLAlchemy Anti-Patterns](#sqlalchemy-anti-patterns)
@ -12,14 +13,12 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
SQLAlchemy Anti-Patterns
========================
# SQLAlchemy Anti-Patterns
This is a list of what I consider [SQLAlchemy](http://www.sqlalchemy.org/)
anti-patterns.
Abusing lazily loaded relationships
-----------------------------------
## Abusing lazily loaded relationships
Bad:
@ -34,10 +33,10 @@ class Customer(Base):
This suffers from severe performance inefficiencies:
* The toaster will be loaded, as well as its toast. This involves creating and
- The toaster will be loaded, as well as its toast. This involves creating and
issuing the SQL query, waiting for the database to return, and instantiating
all those objects.
* `has_valid_toast` does not actually care about those objects. It just returns
- `has_valid_toast` does not actually care about those objects. It just returns
a boolean.
A better way would be to issue a SQL `EXISTS` query so that the database
@ -61,8 +60,7 @@ class Customer(Base):
This query might not always be the fastest if those relationships are small,
and eagerly loaded.
Explicit session passing
------------------------
## Explicit session passing
TODO
@ -73,13 +71,11 @@ def toaster_exists(toaster_id, session):
...
```
Implicit transaction handling
-----------------------------
## Implicit transaction handling
TODO
Loading the full object when checking for object existence
----------------------------------------------------------
## Loading the full object when checking for object existence
Bad:
@ -90,8 +86,8 @@ def toaster_exists(toaster_id):
This is inefficient because it:
* Queries all the columns from the database (including any eagerly loaded joins)
* Instantiates and maps all data on the Toaster model
- Queries all the columns from the database (including any eagerly loaded joins)
- Instantiates and maps all data on the Toaster model
The database query would look something like this. You can see that all columns
are selected to be loaded by the ORM.
@ -123,8 +119,7 @@ FROM toasters
WHERE toasters.id = 1) AS anon_1
```
Using identity as comparator
----------------------------
## Using identity as comparator
Bad:
@ -169,8 +164,8 @@ toasters = session.query(Toaster).filter(Toaster.deleted_at.is_(None)).all()
```
See docs for
[is_](http://docs.sqlalchemy.org/en/rel_1_0/core/sqlelement.html#sqlalchemy.sql.operators.ColumnOperators.is_).
[is\_](http://docs.sqlalchemy.org/en/rel_1_0/core/sqlelement.html#sqlalchemy.sql.operators.ColumnOperators.is_).
## Returning `None` instead of raising a `NoResultFound` exception
See [Returning nothing instead of raising NotFound exception](https://github.com/charlax/antipatterns/blob/master/code-antipatterns.md#returning-nothing-instead-of-raising-notfound-exception).
See [Returning nothing instead of raising NotFound exception](https://github.com/charlax/antipatterns/blob/master/code-antipatterns.md#returning-nothing-instead-of-raising-notfound-exception).

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Test antipatterns](#test-antipatterns)
@ -15,31 +16,25 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Test antipatterns
=================
# Test antipatterns
Testing implementation
----------------------
## Testing implementation
TODO
Testing configuration
---------------------
## Testing configuration
TODO
Testing multiple things
-----------------------
## Testing multiple things
TODO
Repeating integration tests for minor variations
------------------------------------------------
## Repeating integration tests for minor variations
TODO
Over-reliance on centralized fixtures
-------------------------------------
## Over-reliance on centralized fixtures
Bad:
@ -71,7 +66,7 @@ efficient part of the developer flow.
Lastly, this separate the setup and running part of the tests. It makes it more
difficult for a new engineer to understand what is specific about this test's
setup without having to open the ``fixtures`` file.
setup without having to open the `fixtures` file.
Here's a more explicit way to do this. Most fixtures libraries allow you to
override default parameters, so that you can make clear what setup is specific
@ -83,13 +78,11 @@ def test_stuff():
toaster_with_color_blue.toast('brioche')
```
Over-reliance on replaying external requests
--------------------------------------------
## Over-reliance on replaying external requests
TODO
Inefficient query testing
-------------------------
## Inefficient query testing
Bad:
@ -125,9 +118,7 @@ to verify the behavior.
One would also recommend to not do this kind of integration testing for queries
going to the database, but sometimes it's a good tradeoff.
Assertions in loop
------------------
## Assertions in loop
Bad:
@ -159,22 +150,22 @@ at our test trying to do too many things.
![Test Pyramid](/images/test-pyramid.png)
*The [test pyramid](https://martinfowler.com/bliki/TestPyramid.html). Image courtesy of Martin Fowler.*
_The [test pyramid](https://martinfowler.com/bliki/TestPyramid.html). Image courtesy of Martin Fowler._
Building lots of automated comprehensive end-to-end tests was tried multiple time, and almost never worked.
* End to end tests try to do too many things, are highly indeterministic and as a result very flakey.
* Debugging end to end tests failure is painful and slow - this is usually where most of the time is wasted.
* Building and maintaining e2e tests is very costly.
* The time to run e2e tests is much much longer than unit tests.
- End to end tests try to do too many things, are highly indeterministic and as a result very flakey.
- Debugging end to end tests failure is painful and slow - this is usually where most of the time is wasted.
- Building and maintaining e2e tests is very costly.
- The time to run e2e tests is much much longer than unit tests.
Focused testing (e.g. unit, component, etc) prior to roll out, and business monitoring with alerting are much more efficient.
More reading on this topic:
* [End-To-End Testing Considered Harmful](http://www.alwaysagileconsulting.com/articles/end-to-end-testing-considered-harmful/), Always Agile Consulting
* [Just Say No to More End-to-End Tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html), Google Testing Blog
* [Testing Strategies in a Microservice Architecture](https://martinfowler.com/articles/microservice-testing/#testing-end-to-end-tips), section titled "Writing and maintaining end-to-end tests can be very difficult", Toby Clemson, MartinFowler.com
* [Introducing the software testing ice-cream cone (anti-pattern)](https://watirmelon.blog/2012/01/31/introducing-the-software-testing-ice-cream-cone/), Alister Scott
* [TestPyramid](https://martinfowler.com/bliki/TestPyramid.html), Martin Fowler
* [professional-programming's testing section](https://github.com/charlax/professional-programming#testing)
- [End-To-End Testing Considered Harmful](http://www.alwaysagileconsulting.com/articles/end-to-end-testing-considered-harmful/), Always Agile Consulting
- [Just Say No to More End-to-End Tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html), Google Testing Blog
- [Testing Strategies in a Microservice Architecture](https://martinfowler.com/articles/microservice-testing/#testing-end-to-end-tips), section titled "Writing and maintaining end-to-end tests can be very difficult", Toby Clemson, MartinFowler.com
- [Introducing the software testing ice-cream cone (anti-pattern)](https://watirmelon.blog/2012/01/31/introducing-the-software-testing-ice-cream-cone/), Alister Scott
- [TestPyramid](https://martinfowler.com/bliki/TestPyramid.html), Martin Fowler
- [professional-programming's testing section](https://github.com/charlax/professional-programming#testing)

View File

@ -1,5 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Why use feature flags?](#why-use-feature-flags)
@ -8,31 +9,29 @@
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Why use feature flags?
======================
# Why use feature flags?
* **Velocity**: coupled with a system to rapidly deploy configuration change,
- **Velocity**: coupled with a system to rapidly deploy configuration change,
it is usually much faster to disable new code by turning off a feature than
re-deploying all the code. This extra safety net helps developers be more
confident in their release, because they know they can roll back a change
very rapidly in case of error.
* **Testing**: the presence of a feature flag forces the feature owner to test
- **Testing**: the presence of a feature flag forces the feature owner to test
the flow. Without feature flags, the developer might deploy the change and
assume the absence of errors means the release was successful. Yet there's
numerous failure mode that don't raise explicit errors.
* **Code iterations**: because code can be kept hidden behind a feature flag
- **Code iterations**: because code can be kept hidden behind a feature flag
until it's ready to go live, developers can push smaller code changes that
are not fully integrated yet. Smaller pull requests ease the job of code
reviewers, make testing easier, and reduce the probability of a catastrophic
failure.
* **Gradual rollout**: feature flags enable gradual rollout, where a piece of
- **Gradual rollout**: feature flags enable gradual rollout, where a piece of
code is gradually activated, for instance on a per city basis, or on a per
user group basis. This builds confidence in the feature release process, and
allows the engineer to verify that the new implementation is actually better
(for instance, when coupled with A/B testing frameworks).
Should feature flags be used for everything?
--------------------------------------------
## Should feature flags be used for everything?
I don't think so. I think it's a matter of good judgment. Just like 100% test
coverage does not always make sense (provided lines that are not tested are
@ -41,24 +40,23 @@ decision.
When would I not use a feature flag?
* Simple changes: copy, logging, etc.
* When rolling back takes a few seconds
* Feature that is used only in asynchronous jobs that are safe to retry and
- Simple changes: copy, logging, etc.
- When rolling back takes a few seconds
- Feature that is used only in asynchronous jobs that are safe to retry and
don't impact the user experience.
When should a feature flag be used?
* Large refactors
* Changing integration points
* Performance optimization
* New flows
- Large refactors
- Changing integration points
- Performance optimization
- New flows
References
----------
## References
* Martin Fowler,
- Martin Fowler,
[FeatureToggle](http://martinfowler.com/bliki/FeatureToggle.html)
* Flickr, [Flipping Out](http://code.flickr.net/2009/12/02/flipping-out/): one
- Flickr, [Flipping Out](http://code.flickr.net/2009/12/02/flipping-out/): one
of the first articles on the topic.
* [Using Feature Flags to Ship Changes with
- [Using Feature Flags to Ship Changes with
Confidence](http://blog.travis-ci.com/2014-03-04-use-feature-flags-to-ship-changes-with-confidence/)

View File

@ -1,23 +1,24 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Glossary](#glossary)
- [Second system effect](#second-system-effect)
- [Second system effect](#second-system-effect)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Glossary
* **Blackbox monitoring**
* **Conceptual integrity**: "It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas." ([Fred Brooks](http://wiki.c2.com/?ConceptualIntegrity))
* **End-to-end principle**: "application-specific features reside in the communicating end nodes of the network, rather than in intermediary nodes" ([end-to-end principle - Wikipedia](https://en.wikipedia.org/wiki/End-to-end_principle))
* **[NIHITO](http://pragmaticmarketing.com/resources/use-the-market-to-gain-credibility)** (Nothing Important Happens In The Office): you need to learn from your customers and from the market first.
* **Stability** is the sensitivity to change of a given system that is the negative impact that may be caused by system changes ([ISO/IEC 9126 standard on evaluating software quality](https://en.wikipedia.org/wiki/ISO/IEC_9126)).
* **Reliability** is made of maturity (frequency of failure in software), fault tolerance (ability to withstand failure) and recoverability (ability to bring back a failed system to full operation) ([ISO/IEC 9126 standard on evaluating software quality](https://en.wikipedia.org/wiki/ISO/IEC_9126)).
* **Two Generals' Problem**: "a thought experiment meant to illustrate the pitfalls and design challenges of attempting to coordinate an action by communicating over an unreliable link" ([wikipedia](https://en.wikipedia.org/wiki/Two_Generals%27_Problem)).
* **Whitebox monitoring**
* **Law of Demeter**: only talk to your immediate friends ([wikipedia](https://en.wikipedia.org/wiki/Law_of_Demeter))
- **Blackbox monitoring**
- **Conceptual integrity**: "It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas." ([Fred Brooks](http://wiki.c2.com/?ConceptualIntegrity))
- **End-to-end principle**: "application-specific features reside in the communicating end nodes of the network, rather than in intermediary nodes" ([end-to-end principle - Wikipedia](https://en.wikipedia.org/wiki/End-to-end_principle))
- **[NIHITO](http://pragmaticmarketing.com/resources/use-the-market-to-gain-credibility)** (Nothing Important Happens In The Office): you need to learn from your customers and from the market first.
- **Stability** is the sensitivity to change of a given system that is the negative impact that may be caused by system changes ([ISO/IEC 9126 standard on evaluating software quality](https://en.wikipedia.org/wiki/ISO/IEC_9126)).
- **Reliability** is made of maturity (frequency of failure in software), fault tolerance (ability to withstand failure) and recoverability (ability to bring back a failed system to full operation) ([ISO/IEC 9126 standard on evaluating software quality](https://en.wikipedia.org/wiki/ISO/IEC_9126)).
- **Two Generals' Problem**: "a thought experiment meant to illustrate the pitfalls and design challenges of attempting to coordinate an action by communicating over an unreliable link" ([wikipedia](https://en.wikipedia.org/wiki/Two_Generals%27_Problem)).
- **Whitebox monitoring**
- **Law of Demeter**: only talk to your immediate friends ([wikipedia](https://en.wikipedia.org/wiki/Law_of_Demeter))
### Second system effect

View File

@ -17,11 +17,11 @@ console.log("hello");
```javascript
// ???
console.assert('1' == 1)
console.assert("1" == 1);
// Better
console.assert(!('1' === 1))
console.assert('1' !== 1)
console.assert(!("1" === 1));
console.assert("1" !== 1);
```
#### Comparing non-scalar
@ -29,22 +29,22 @@ console.assert('1' !== 1)
Applied on arrays and objects, `==` and `===` will check for object identity, which is almost never what you want.
```javascript
console.assert({a: 1} != {a: 1})
console.assert({a: 1} !== {a: 1})
console.assert({ a: 1 } != { a: 1 });
console.assert({ a: 1 } !== { a: 1 });
const obj = {a: 1}
const obj2 = obj
console.assert(obj == obj2)
console.assert(obj === obj2)
const obj = { a: 1 };
const obj2 = obj;
console.assert(obj == obj2);
console.assert(obj === obj2);
```
Use a library such as [lodash](https://lodash.com/) to properly compare objects and array
```javascript
import _ from 'lodash'
import _ from "lodash";
console.assert(_.isEqual({a: 1}, {a: 1}))
console.assert(_.isEqual([1, 2], [1, 2]))
console.assert(_.isEqual({ a: 1 }, { a: 1 }));
console.assert(_.isEqual([1, 2], [1, 2]));
```
### `Object` methods
@ -62,34 +62,34 @@ console.assert(_.isEqual([1, 2], [1, 2]))
### Objects
```javascript
const toaster = {size: 2, color: 'red', brand: 'NoName'};
const toaster = { size: 2, color: "red", brand: "NoName" };
// Get one object key
const {size} = toaster;
console.assert(size === 2)
const { size } = toaster;
console.assert(size === 2);
// Get the rest with ...rest
const {color, brand, ...rest} = toaster;
console.assert(_.isEqual(rest, {size: 2}));
const { color, brand, ...rest } = toaster;
console.assert(_.isEqual(rest, { size: 2 }));
// Set default
const {size2 = 3} = toaster
console.assert(size2 === 3)
const { size2 = 3 } = toaster;
console.assert(size2 === 3);
// Rename variables
const {size: size3} = toaster
console.assert(size3 === 2)
const { size: size3 } = toaster;
console.assert(size3 === 2);
// Enhances object literals
const name = 'Louis'
const person = {name}
console.assert(_.isEqual(person, {name: 'Louis'}))
const name = "Louis";
const person = { name };
console.assert(_.isEqual(person, { name: "Louis" }));
// Dynamic properties
const person2 = {['first' + 'Name']: 'Olympe'}
console.assert(_.isEqual(person2, {firstName: 'Olympe'}))
const person2 = { ["first" + "Name"]: "Olympe" };
console.assert(_.isEqual(person2, { firstName: "Olympe" }));
// Btw, you can include quotes although nobody does this
console.assert(_.isEqual(person2, {'firstName': 'Olympe'}))
console.assert(_.isEqual(person2, { firstName: "Olympe" }));
```
### Array
@ -107,29 +107,29 @@ console.assert(_.isEqualWith(rest, [3]));
## `let` and `const`
```javascript
const constantVar = 'a';
const constantVar = "a";
// Raises "constantVar" is read-only
constantVar = 'b';
constantVar = "b";
let mutableVar = 'a';
mutableVar = 'a';
let mutableVar = "a";
mutableVar = "a";
// Note: this will work ok
const constantObject = {a: 1}
constantObject.a = 2
constantObject.b = 3
const constantObject = { a: 1 };
constantObject.a = 2;
constantObject.b = 3;
// Raises: "constantObject" is read-only
constantObject = {a: 1}
constantObject = { a: 1 };
// const and let are block scoped. A block is enclosed in {}
{
const a = 'a';
console.log({a})
const a = "a";
console.log({ a });
}
// Raises: ReferenceError: a is not defined
console.log({a})
console.log({ a });
```
Note: try to use `const` as much as you can.
@ -152,29 +152,29 @@ The first advantage of arrow function is that they're shorter to write:
```javascript
// You can define a function this way:
const myFunction = function() {
const myFunction = function () {
console.log("hello world");
}
};
// With an arrow function, you save a few characters:
const myArrowFunction = () => {
console.log("hello world");
}
};
// Some things, like params parentheses, and function code brackets, are optional
const myFunctionToBeShortened = function(a) {
const myFunctionToBeShortened = function (a) {
return a;
}
};
// Shorter arrow function
const myFunctionToBeShortenedArrowV1 = (a) => {
return a;
}
};
// Shortest arrow function
// Remove single param parenthesis, remove function code bracket, remove return
const myFunctionToBeShortenedArrowV2 = a => a
console.assert(myFunctionToBeShortenedArrowV2(1) === 1)
const myFunctionToBeShortenedArrowV2 = (a) => a;
console.assert(myFunctionToBeShortenedArrowV2(1) === 1);
```
### How `this` works in arrow functions