¿Code Smells? y eso... ¿con qué se come?

  • 🥟 Entrada

    • 🥖 ¿Cómo mejoro la calidad de mi código?
    • 🧈 Diseño con Objetos
  • 🍽️ Plato principal

    • 🌶️ Problema
    • 🍷 Cuatro Niveles de Código
    • 🍝 Reconociendo Code Smells
  • 🥄 Postre

    • ☕ Extendiendo el código
    • 🍮
  • 🥡 Para llevar

ENTRADA

¿Cómo mejoro la calidad de mi código?

¿Cómo mejoro juzgo la calidad de mi código?

Estilo:

- PEP8

Principios:

  • DRY (Don't Repeat Yourself // No te repitas)
  • YAGNI (You aren't gonna need it// No lo vas a usar)
  • GRASP (General Responsibility Assignment Software Patterns//...)
  • SOLID

Diseño con Objetos (Object Oriented Design)

'En Python todo es un Objeto'

huevo_objeto

homunculo

Homunculo, extraido de Smalltalk, Objects and Design. Chamond Liu.

👤

In [2]:
'humunculo', 1, print
Out[2]:
('humunculo', 1, <function print>)

👤 🖃

In [3]:
'humunculo'.upper()
Out[3]:
'HUMUNCULO'

👤 💌

In [4]:
'humunculo'.split('u')
Out[4]:
['h', 'm', 'nc', 'lo']

👤 📨

👤 💌 🖃 👤 ✉️ 🧧 👤

Why objects matter

When all the rhetoric is set aside -the rhetoric about reusability and productivity and so on- the salient charateristic of objects is that they reduce translation. That is, objects promote a common vocabulary: everyone, whether a software professional or not, has some intuitive understanding of what an object is. Thus we can understand one another more easil when we use objects to describe our thinking. Objects, then, promote mutual understanding -between users, analysts, executives, designers, programmers, testers... They reduce the effort of translating one persons'thoughts to another's, and therefore reduce misunderstandings as an idea passes from one person to the next.
    As for reuse and productivity, they are nothing more that side effects of better understanding...


Smaltalk, Objects and Design. Chamond Liu

Carbonara

Epicurious 4 Levels of

🌶️ Problema

huevo_frito

huevo_frito

Huevos a la carta
-----------------

Huevo duro $0.1
Huevo frito $0.2

Quiero que imprima la orden completa y el total

In [5]:
!cat ordenes.txt
orden, cantidad, estilo
1, 1, duro
2, 2, frito

Cuatro niveles de Codigo

🤔 Novate

In [6]:
from soluciones.chef_nivel_uno import solucion 
solucion.ejecutar()
1 pidio 1 huevo duro

2 pidio 2 huevo frito
Total: 3.4

👩🏽‍💻 Preparade

In [7]:
from soluciones.chef_nivel_dos.solucion import CocinarHuevos
CocinarHuevos().ejecutar()
La orden 1 es de 1 huevo 🥚
La orden 2 es de 2 huevos 🍳
El total a pagar es 0.5 pesos.

🤨 Experte

In [8]:
from soluciones.chef_nivel_tres.solucion import CocinarHuevos
CocinarHuevos().ejecutar()
La orden 1 es de 1 huevo 🥚
La orden 2 es de 2 huevos 🍳
El total a pagar es 0.5 pesos.

✨ 👩🏽‍🏫 ✨

In [9]:
!tree soluciones/
soluciones/
├── chef_nivel_dos
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-38.pyc
│   │   ├── solucion.cpython-38.pyc
│   │   └── test.cpython-38-pytest-6.0.1.pyc
│   ├── solucion.py
│   └── test.py
├── chef_nivel_tres
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-38.pyc
│   │   ├── solucion.cpython-38.pyc
│   │   └── test_cocinar_huevos.cpython-38-pytest-6.0.1.pyc
│   ├── solucion.py
│   └── test_cocinar_huevos.py
├── chef_nivel_uno
│   ├── __pycache__
│   │   └── solucion.cpython-38.pyc
│   └── solucion.py
├── __init__.py
└── __pycache__
    └── __init__.cpython-38.pyc

7 directories, 16 files
In [10]:
# %load soluciones/chef_nivel_uno/solucion.py
precios = (1.0, 1.2) # Precios : ( duro, frito )

def ejecutar():
    'Cocinar Huevos'
    
    #Cargar ordenes
    O = []
    try:
        with open('ordenes.txt','r') as f:
            i = 0
            for l in f:
#                 print(l)
                if i == 0:
                    i += 1
                    continue
                else:
#                     print(l)
#                     print(l[6:])
                    if (l[6:] == 'duro') or (l[6:] == 'duro\n'):
                        print(f'{l[0]} pidio {l[3]} huevo {l[6:]}')
                        O.append( ('duro', int(l[3]) ) )
                    elif (l[6:] == 'frito') or (l[6:] == 'frito\n'):
                        print(f'{l[0]} pidio {l[3]} huevo {l[6:]}')
                        O.append( ('frito', int(l[3])) )
                i += 1
    except:
        print('No se pudieron cargar las ordenes. Ahhh!!')
#     print(O)
    
    # Calcular total
    t = 0
    for i in O:
        if i[0] == 'duro':
            t += precios[0]*i[1]
        else:
            t += precios[1]*i[1]
            
    print(f'Total: {t}')


if __name__ == '__main__':
    ejecutar()
1 pidio 1 huevo duro

2 pidio 2 huevo frito
Total: 3.4
In [11]:
# %load soluciones/chef_nivel_dos/solucion.py
import csv
from collections import Counter, namedtuple

Orden = namedtuple('Orden','orden cantidad estilo')

class CocinarHuevos:

    PRECIOS = { 'duro' : 1.0, 
                'frito': 1.2,
              }
    
    REPR = { 'duro'  : '\N{egg}',
             'frito' : '\N{cooking}',
           }
    
    @staticmethod
    def ejecutar(archivo='ordenes.txt'):
        with open(archivo) as f:
            csv_reader = csv.reader(f)
            ordenes = list(csv_reader)[1:]
            
            cuentas = Counter( row[2] for row in ordenes for _ in range(int(row[1].strip())) )
            total = sum( CocinarHuevos.PRECIOS.get(k.strip(),0)*v for k,v in cuentas.items()  )
            
            CocinarHuevos.informar(ordenes)
            print(f'El total a pagar es {total} pesos.')
            
    @staticmethod
    def informar(ordenes):
        for orden in ordenes:
            orden = Orden(*orden)
            x = ( f'huevo {CocinarHuevos.REPR.get(orden.estilo.strip())}'
                   if int(orden.cantidad.strip()) == 1 
                   else f'huevos {CocinarHuevos.REPR.get(orden.estilo.strip())}' )
            print(f'La orden {orden.orden} es de {orden.cantidad.strip()} {x}')

Patrones

Patrones de Diseño

In [12]:
# %load soluciones/chef_nivel_dos/test.py
from .solucion import CocinarHuevos

esperado = '''La orden 1 es de 1 huevo 🥚
La orden 2 es de 2 huevos 🍳
El total a pagar es 3.4 pesos.
'''

def test_CocinarHuevos(capsys):
    CocinarHuevos().ejecutar()
    captured = capsys.readouterr()
    assert captured.out == esperado
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-12-618844e8a431> in <module>
      1 # %load soluciones/chef_nivel_dos/test.py
----> 2 from .solucion import CocinarHuevos
      3 
      4 esperado = '''La orden 1 es de 1 huevo 🥚
      5 La orden 2 es de 2 huevos 🍳

ImportError: attempted relative import with no known parent package
In [ ]:
# %load soluciones/chef_nivel_tres/solucion.py
import csv
from collections import Counter, namedtuple

Orden = namedtuple('Orden','orden cantidad estilo')

class CocinarHuevos:

    PRECIOS = { 'duro' : 1.0, 
                'frito': 1.2,
              }
    
    REPR = { 'duro'  : '\N{egg}',
             'frito' : '\N{cooking}',
           }
    
    @staticmethod
    def ejecutar(archivo='ordenes.txt'):
        with open(archivo) as f:
            csv_reader = csv.reader(f)
            ordenes = list(csv_reader)[1:]
            
            cuentas = Counter( row[2] for row in ordenes for _ in range(int(row[1].strip())) )
            total = sum( CocinarHuevos.PRECIOS.get(k.strip(),0)*v for k,v in cuentas.items()  )
            
            CocinarHuevos.informar(ordenes)
            print(f'El total a pagar es {total} pesos.')
            
    @staticmethod
    def informar(ordenes):
        for orden in ordenes:
            orden = Orden(*orden)
            x = ( f'huevo {CocinarHuevos.REPR.get(orden.estilo.strip())}'
                   if int(orden.cantidad.strip()) == 1 
                   else f'huevos {CocinarHuevos.REPR.get(orden.estilo.strip())}' )
            print(f'La orden {orden.orden} es de {orden.cantidad.strip()} {x}')

omelet

Nuevo requerimiento

Preparar omelet, que cuesta $0.5 y lleva dos huevos

In [ ]:
!cat nuevas_ordenes.csv

Open-Closed (O en SOLID)

Los objetos deben estar abiertos para ser extendidos pero cerrados para ser modificados.

Objects should be open for extension, but closed for modification.

FlowChart Imagen adaptada de 99 Bottles of OOP

Refactoring is the process of changing a software system in such a way that it does  not  alter  the  external  behavior  of  the  code  yet improves  its  internal structure.

Martin Fowler, Refactoring

Code Smells

Alternative Classes with Different Interfaces 
Comments 
Data Class 
Data Clumps 
Divergent Change 
Duplicated Code 
Feature Envy 
Global Data 
Insider Trading 
Large Class 
Lazy Element
Long Function 
Long Parameter List 
Loops 
Message Chains 
Middle Man 
Mutable Data 
Mysterious Name 
Primitive Obsession
Refused Bequest 
Repeated Switches 
Shotgun Surgery 
Speculative Generality 
Temporary Field

Flocking Rules

  1. Select the things that are most alike.
  2. Find the smallest difference between them.
  3. Make the simplest change that will remove that difference.
In [ ]:
!pytest soluciones/chef_nivel_dos/test.py
In [ ]:
# %load soluciones/chef_nivel_dos/solucion.py
import csv
from collections import Counter, namedtuple

Orden = namedtuple('Orden','orden cantidad estilo')

class CocinarHuevos:

    PRECIOS = { 'duro' : 1.0, 
                'frito': 1.2,
              }
    
    REPR = { 'duro'  : '\N{egg}',
             'frito' : '\N{cooking}',
           }
    
    @staticmethod
    def ejecutar(archivo='ordenes.txt'):
        with open(archivo) as f:
            csv_reader = csv.reader(f)
            ordenes = list(csv_reader)[1:]
            
            cuentas = Counter( row[2] for row in ordenes for _ in range(int(row[1].strip())) )
            total = sum( CocinarHuevos.PRECIOS.get(k.strip(),0)*v for k,v in cuentas.items()  )
            
            CocinarHuevos.informar(ordenes)
            print(f'El total a pagar es {total} pesos.')
            
    @staticmethod
    def informar(ordenes):
        for orden in ordenes:
            orden = Orden(*orden)
            x = ( f'huevo {CocinarHuevos.REPR.get(orden.estilo.strip())}'
                   if int(orden.cantidad.strip()) == 1 
                   else f'huevos {CocinarHuevos.REPR.get(orden.estilo.strip())}' )
            print(f'La orden {orden.orden} es de {orden.cantidad.strip()} {x}')

FLAN

sashaKile

🥡 Ordenes para llevar

Lo que esta charla quizo ser y no fue

Charla por Katrina Owen

Succession

Charlas por Sandi Metz:

Nothing is Something

SOLID Object-Oriented Design

Polly want a message

Get a Whiff of This

The Magic Tricks of Testing

provecho