Não é fácil escrever uma biblioteca confortável de usar. É difícil agradar a todos. Mas também é difícil agradar até quem acha que a nossa biblioteca faz algo útil. Não é fácil nem mesmo quando fazemos algo reusável só para nós mesmos.
Ainda bem que muitos outros programadores já erraram antes de nós. E existem em Python e fora dele diversos exemplos de boas libs nas quais podemos nos inspirar.
Essa palestra resumirá o que torna uma biblioteca boa, de acordo com nossa experiência e com a experiência de outros programadores que já escreveram sobre isso. Algumas características de boas bibliotecas são:
- Alta Consistência
- Muitos dados puros
- Baixa Verbosidade
- Respeito ao Principle of Least Astonishment
- Alta Extensibilidade
- Baixa Retenção
- Vários níveis de abstrações
- Alta granularidade
- Interesses claros e separados
- Pythonica
Como você pode ver, alguns aspectos acima são similares ao Zen of Python. Por isso também mostraremos funcionalidades do Python que ajudam a programar boas interfaces. Além disso, para resumir tudo definiremos um checklist que você poderá usar sempre que for escrever um módulo reusável. Esperamos que isso ajude você a programar melhor, o que certamente agradará seus parceiros de trabalho e a comunidade.
1. Como fazer boas libs?
Flávio Juvenal
Vinta Software
www.vinta.com.br
2.
3.
4. ● Dia 16: Estrutura de dados e collections em Python -
André Ericson (Sala Sambaqui 3)
● Dia 17: Definindo um Boilerplate Customizável usando
Django, React e Bootstrap - Lais Varejão (Sala Sambaqui 3)
Outras talks da Vinta na PyBR 12
6. Não vamos abordar
● Estrutura de projeto
● Distribuição
● Licença de uso
● Semantic Versioning
● Code-style
● Continuous Integration
● Coverage
● etc...
7. API
"Um conjunto de definições de subrotinas, protocolos e
ferramentas para construir software e aplicações."
https://en.wikipedia.org/wiki/Application_programming_inter
face
10. Sua API é uma UI
10 Usability Heuristics for User Interface Design
https://www.nngroup.com/articles/ten-usability-heuristics/
11. Sua API é uma UI
● Visibility of system status
● Match between system and the real world
● User control and freedom
● Consistency and standards
● Recognition rather than recall
● Flexibility and efficiency of use
● Aesthetic and minimalist design
● Error prevention
● Help users recognize, diagnose, and recover from errors
● Help and documentation
12. Sua API é uma UI
Hmm… bonito… mas abstrato demais...
Máximas como esta são difíceis de aplicar e avaliar.
Precisamos de princípios mais práticos!
14. - Com ou sem underscore?
gettype VS get_class (PHP)
- objeto + verbo ou verbo + objeto?
str_shuffle VS recode_string (PHP)
Consistência na prática: nomenclatura
17. - Significado de vazio: None, False, [], '', 0
- Use o mais adequado. Cuidado com significados
obscuros:
>>> import datetime
>>> bool(datetime.date(datetime.MINYEAR, 1, 1))
True
>>> bool(datetime.time(0)) # Python < 3.5
False
Consistência na prática: semântica
18. [ ] A API exige que o usuário volte
constantemente para documentação para saber como
faz algo?
[ ] Existe consistência na nomenclatura (ordem
dos termos, abreviações, plurais, etc)?
[ ] Existe consistência na ordem dos argumentos?
[ ] Existe consistência no que significa vazio?
Aumentar a consistência
20. Zen of Python
Flat is better than nested.
Sparse is better than dense.
Readability counts.
21. [ ] A API exige que você copie e cole muito
código para fazer algo comum? Se sim, faça
funções mais gerais (mas mantenha as mais
específicas para não perder composability).
Exemplo:
Reduzir a verbosidade
24. [ ] É possível usar um tipo padrão adequado ao
invés de um novo tipo customizado da API?
urllib.request.HTTPPasswordMgrWithDefaultRealm VS tuple
Reduzir a verbosidade
25. [ ] A API tem valores defaults sãos?
// Aff...
history.pushState(null, "", "https://vinta.com.br");
// Por que não é?
history.pushState("https://vinta.com.br");
Reduzir a verbosidade
27. Principle of Least Astonishment
Previna que seu cliente da API fale WAT.
http://programmers.stackexchange.com/questions/187457/
what-is-the-principle-of-least-astonishment
28. JavaScript < ES5
>>> parseInt('010')
>>> 8
Se o valor vier de um input do usuário, lascou…
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects
/parseInt#ECMAScript_5_removes_octal_interpretation
Principle of Least Astonishment
32. [ ] PLA funcional: O comportamento padrão faz o
que o usuário realmente espera? Testar com
usuários reais!
Garantir o Principle of Least Astonishment
33. Em Python, "assimetria de comportamentos deve se refletir
em assimetria de formas":
numbers.sort() VS sorted(numbers)
.append() VS +
Evitar misturar retornos com side-effects:
http://stackoverflow.com/questions/13062423/is-making-in-place-operations-ret
urn-the-object-a-bad-idea
Principle of Least Astonishment
34. Cuidado com falsa simetria:
QStatusBar::message(text, msecs) (Qt 3)
QStatusBar::setMessage(text, msecs)
QStatusBar::showMessage(text, msecs) (Qt 4)
Principle of Least Astonishment
35. [ ] PLA não-funcional: O comportamento padrão
está de acordo com as expectativas de
performance, efeitos colaterais, safety,
segurança, etc?
Garantir o Principle of Least Astonishment
37. "Abstração: uma simplificação de algo muito mais complicado
que está acontecendo por debaixo dos panos"
Joel Spolsky em
http://www.joelonsoftware.com/articles/LeakyAbstractions.h
tml
Abstrações
38. from collections import Counter
c = Counter(['eggs', 'ham', 'eggs'])
print(c)
>>> Counter({'eggs': 2, 'ham': 1})
Abstrações
40. PS: Mas só porque está acontecendo debaixo dos panos, não
deve ser completamente invisível. O como às vezes precisa
estar evidente.
Mais sobre isso a seguir...
Law of Leaky Abstractions
41. O problema é que "Todas abstrações não-triviais são
vazadas".
Esta é a "The Law of Leaky Abstractions".
Ou seja, detalhes da implementação vazam.
Law of Leaky Abstractions
42. O que causa vazamentos?
Quando tentamos garantir algo impossível ou não-natural.
Law of Leaky Abstractions
43. APIs RPC que tentam igualar chamadas remotas a chamadas
locais.
guido_profile = Facebook.get_profile('guido')
guido_profile.set_name("Benevolent Dictator For Life")
Law of Leaky Abstractions
44. REST não abstrai a
dualidade cliente-servidor.
Pelo contrário, abraça ela.
Por exemplo com o
Conditional PUT:
Law of Leaky Abstractions
45. [ ] Existe algo que a API está tentando abstrair
e não devia?
Minimizar vazamentos das abstrações em APIs
47. Outro problema das abstrações é quando elas não são
completas, são restritas.
O cliente precisará estender a abstração com mais
funcionalidades, mudar partes de funcionalidades existentes
ou compor a abstração com outras.
Completude e extensibilidade
48. Completude e extensibilidade
"O maior objetivo de uma API deve ser eliminar a
descontinuidade de integração."
Casey Muratori em:
https://mollyrocket.com/casey/stream_0028.html
49.
50.
51. ORM count
ORM annotate com
F object
ORM com
annotate e
custom
Aggregate
ORM annotate
com F object e
Func
52.
53.
54. O cliente precisará estender a abstração com mais
funcionalidades, mudar partes de funcionalidades existentes
ou compor a abstração com outras.
Não tem problema a API não ser completa, mas ela deve ser
extensível, flexível e "composable".
Completude e extensibilidade
56. Um dos vilões da extensibilidade se chama retenção.
Retenção ocorre quando a API sequestra ou encapsula um
recurso e impede seu uso direto.
Retenção
57. - retenção para ser + extensível
[ ] Ofereça a opção de deixar um recurso interno
aberto/alocado mesmo depois de ter terminado de
usá-lo.
[ ] Dê acesso (mesmo que com _) a recursos que o
cliente pode precisar.
58. [ ] Retorne objetos que o cliente pode precisar.
[ ] Ao fazer Inversion of Control ou chamar
callbacks, passe tudo que a função do cliente
pode precisar.
Exemplo a seguir:
- retenção para ser + extensível
62. Além de mais retenção, encapsulamento total resulta em menos
flexibilidade. Quem quiser mudar o comportamento interno vai
conseguir de um jeito ou de outro (monkey-patching, reflection,
etc)… então pra que esconder tanto?
"For a Pythonista, encapsulation is not the inability of seeing
internals of classes, but the possibility of avoiding to look at it"
http://stackoverflow.com/questions/7456807/python-name-mangl
ing
Encapsulamento total para que?
64. - encapsulamento para ser + extensível
[ ] A API permite a modificação ou remoção de
comportamentos sem que o usuário tenha que
reescrever o código interno dela? Permita
sobrescrita para evitar reescrita!
Exemplo dropzone.js:
65.
66. - encapsulamento para ter + composability
[ ] Aceite recursos oferecidos pelo cliente caso
eles sejam similares aos seus (aproveite
duck-typing).
[ ] Ofereça a opção de aceitar um recurso externo
previamente aberto/alocado.
67. [ ] Se for necessário fazer muitos mock.patch,
então está faltando Inversion of
Control/Dependecy Injection para ser mais
flexível. Faça mais DI.
Exemplo:
- encapsulamento para ter + composability
74. + níveis de abstração para ter + composability
[ ] A API provê vários níveis de abstração, do
mais abstrato ao mais específico/customizável/
baixo-nível?
[ ] Na API, você deixou "o simples fácil, o
complexo possível e o errado impossível"?
Exemplo a seguir:
75. + níveis de abstração para ter + composability
@app.task
def add(x, y):
return x + y
79. [ ] Tem "and" no nome da função? A função muda o
estado de vários objetos? A função tem mais de um
interesse (concern)? Se sim, quebre a função
maior fazendo ela chamar funções menores.
Exemplo a seguir:
+ granularidade para ter + composability e + extensibilidade
80.
81. ● Não forçar o usuário a especificar o que é óbvio ou
frequente.
● Mas também não esconder o que é importante e muitos
podem querer mudar.
● Quanto mais níveis de abstração a API tiver, mais a API
vai acertar os níveis que os clientes precisam.
Acertar o nível de abstração
83. ● Prefira @property a métodos
import urllib.request
r = urllib.request.urlopen('http://python.org/')
r.getcode()
import requests
r = requests.get('http://python.org/')
r.status_code
Pythonic
84. ● Use Context managers para objetos com life-cycle
f = open('file.txt')
contents = f.read()
f.close()
with open('file.txt') as f:
contents = f.read()
Pythonic
86. Pythonic
● Operator overloading
● Decorators
● Generators
● asyncio
● etc...
"Design Patterns are missing language features"
Exemplo: strategy pattern < high order functions
87. ● Documentação!!!!!!!!!!
○ Mostrar quick-start com poucas linhas para fazer o
básico
○ Top-down: primeiro pensar em o que a API deve fazer,
documentar isso, e só depois implementar
○ Exemplos hands-on, com casos de uso (exemplo:
python-social-auth)
○ Documentar erros comuns, depois proteger a API
contra eles (se estiver falando muito "note" ou
"careful", mude sua API)
Também é importante
88. ● Visibilidade de contexto: cuidado com interfaces modais
● Exceções
○ Vários tipos
○ Mensagens de erro
○ Fail-fast, especialmente em operações sequenciais
○ "Errors should never pass silently. Unless explicitly
silenced."
● Safety (não é segurança!)
● Imutabilidade
● Discoverability
● etc...
Também é importante
90. ● Designing and Evaluating Reusable Components
● The Little Manual of API Design
● API Design Tips
● API Design
● What Makes Software Good?
● What makes a good software library?
● Bumper-Sticker API Design
● Ten Good Rules for Bad APIs
Referências
91. Referências
● What guidelines should I follow while designing a library?
● API: Design Matters
● How to Design a Good API and Why it Matters
● API Design Principles - Qt Wiki
● PHP: a fractal of bad design
● Why frameworks are evil
● Multiple levels of abstraction
● The Law of Leaky Abstractions
92. Referências
● On DRY and the cost of wrongful abstractions
● The Wrong Abstraction
● Separation of concerns
● Command–query separation
● PEP 8