Run prettier everywhere
This commit is contained in:
parent
6f941a8ab3
commit
c328d43192
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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`.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -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)
|
||||
|
@ -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/)
|
||||
|
21
glossary.md
21
glossary.md
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user