Clean Code
@gruizdevilla
private static int k(int k, int kk) {
String kkkkkkkk = Integer.toBinaryString(k);
String kkkkkkk = Integer.toBinaryString(kk);
// s1, s2 are binary representations
while(kkkkkkkk.length() > kkkkkkk.length())
kkkkkkk = "0" + kkkkkkk;
while(kkkkkkkk.length() < kkkkkkk.length())
kkkkkkkk = "0" + kkkkkkkk;
int kkkkkkkkk = 0;
for(int kkkkkkkkkk = 0; kkkkkkkkkk < kkkkkkkk.length(); kkkkkkkkkk++) {
if(kkkkkkk.charAt(kkkkkkkkkk) != kkkkkkkk.charAt(kkkkkkkkkk))
kkkkkkkkk++;
}
return kkkkkkkkk;
}
🤦♀️
What is clean code?
The logarithmic rule of ⌛️ in software development
1x write code
10x fix code
100x read code
* completely invented numbers, but they feel realistic, doesn't they?
Bug cost
SELECT note as note_original,
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(note, '\"', ''),
'.', ''),
'?', ''),
'`', ''),
'<', ''),
'=', ''),
'{', ''),
'}', ''),
'[', ''),
']', ''),
'|', ''),
'\'', ''),
':', ''),
';', ''),
'~', ''),
'!', ''),
'@', ''),
'#', ''),
'$', ''),
'%', ''),
'^', ''),
'&', ''),
'*', ''),
'_', ''),
'+', ''),
',', ''),
'/', ''),
'(', ''),
')', ''),
'-', ''),
'>', ''),
' ', '-'),
'--', '-') as note_changed FROM invheader
🤦♀️
Busquemos inspiración en algunas definiciones
BJARNE STROUSTRUP
(Inventor de C++)
Elegante y eficiente.
Lógica directa, mínimas dependencias y fácil de mantener.
GRADY BOOCH
(Desarrolló UML)
El código limpio se lee como prosa bien escrita
"BIG" DAVE THOMAS
(Padrino de Eclipse)
- El código limpio puede ser leído y mejorado
por un desarrollador distinto de su autor original. - Tiene tests unitarios y de aceptación.
- Tiene nombres con significado.
- Proporciona una forma de hacer las cosas en lugar
de muchas alternativas.
MICHAEL FEATHERS
(Autor de "Working Effectively with Legacy Code")
- El código limpio parece estar hecho
por alguien a quien le importa.
RON JEFFRIES
(Extreme Programming Adventures in C#)
- Pasa todos los tests
- No tiene duplicidades
- Expresa las ideas de diseño del sistema
- Minimiza el número de entidades
como clases, métodos y similares
WARD CUNNINGHAM
(Inventor de la Wiki y mucho más)
- Sabes que estás trabajando con código limpio
cuando cada rutina que lees resulta ser como lo
que esperabas encontrarte. - Cuando parece que el lenguaje fue hecho para
el problema que resuelve el código.
Cada cambio en el código:
lo mejora o lo empeora
LA REGLA DEL BOY SCOUT
Porque no es suficiente escribir buen código.
El código se tiene que mantener sin pudrirse
ni degradarse.
"Deja el lugar donde acampaste un poco
más limpio que como lo encontraste."
🤦♀️
NOMBRES SIGNIFICATIVOS
Significativo: que da a entender
o conocer con precisión.
NOMBRES QUE REVELEN INTENCIÓN
- por qué existe?
- ¿qué hace?
- ¿cómo se usa?
int d; //dias transcurridos
int diasTranscurridos;
int diasDesdeCreacion;
int diasDesdeModificacion;
EVITA DESINFORMACIÓN
cuentasList; //Si no es de tipo List....
XYZControllerConManejoEficienteDeStrings;
XYZControllerConAlmacenamientoEficienteDeStrings;
¿Nombre cortos?
for(let i = 0; i < max-1; i++){
for(let j = i+1; j < max; i++){
//lógica
}
}
for(let posicion_1 = 0; posicion_1 < max-1; posicion_1++){
for(let posicion_2 = posicion_2 + 1; j < max; posicion_2++){
//lógica
}
}
HAZ DISTINCIONES QUE APORTEN VALOR
public void copiarObjeto(Clase1 o1, Clase1, o2);
//vs
public void copiarObjeto(Clase1 origen, Clase1 destino);
PRONUNCIABLE
public void copiarObjeto(Clase1 o1, Clase1, o2);
//vs
public void copiarObjeto(Clase1 origen, Clase1 destino);
PRONUNCIABLE
The Walking COBOL!
BUSCABLE
Evita encodings
¿No tenemos lenguajes con tipado estático?
- iContador
- arrNums
- bOcupado
- u32Identificador
(Y para tipado dinámico, apóyate en TDD o extensiones del lenguaje como TypeScript para JavaScript)
Evita mapas mentales
Los lectores no tienen que tener que traducir
tus nombres a otros que entiendan.
La claridad es fundamental.
Nombres de clases
- Deberían ser nombres y no verbos
- Evitar palabras ambiguas como "Manager" ,Gestor", "Procesador", "Controller", "Data", "Info", ...
- Piensa el nombre y no tengas miedo de cambiarlo; al contrario, ten miedo de no cambiarlo....
Nombres de clases
Real life!
- SimpleBeanFactoryAwareAspectInstanceFactory
- AbstractInterceptorDrivenBeanDefinitionDecorator
- AbstractInterruptibleBatchPreparedStatementSetter
- SimpleRemoteStatelessSessionProxyFactoryBean
- TransactionAwarePersistenceManagerFactoryProxy
http://projects.haykranen.nl/java/
Nombres de métodos
- Deben ser un verbo
- Y en castellano además es bueno
que sea imperativo - En el caso de constructores, mejor
que sobrecargar es usar métodos estáticos de factoría (si no hay DI). - Intenta que describa lo que devuelve
- Usa opuestos de forma precisa (Open/Close, Insert/Delete, Start/Stop)
Contexto
APORTA CONTEXTO SI ES NECESARIO
pero
EVITAR CONTEXTO GRATUITO
Evita cosas como:
- Reusar el nombre de una clase dentro de una propiedad: Contacto.ContactoNombre
- Prefijar las clases con el nombre de una aplicación, para eso están los namespaces/packages.
For the lack of a nail,
throw new HorseshoeNailNotFoundException("no nails!");
For the lack of a horseshoe,
EquestrianDoctor.getLocalInstance().getHorseDispatcher().shoot();
For the lack of a horse,
RidersGuild.getRiderNotificationSubscriberList().getBroadcaster().run(
new BroadcastMessage(StableFactory.getNullHorseInstance()));
For the lack of a rider,
MessageDeliverySubsystem.getLogger().logDeliveryFailure(
MessageFactory.getAbstractMessageInstance(
new MessageMedium(MessageType.VERBAL),
new MessageTransport(MessageTransportType.MOUNTED_RIDER),
new MessageSessionDestination(BattleManager.getRoutingInfo(
BattleLocation.NEAREST))),
MessageFailureReasonCode.UNKNOWN_RIDER_FAILURE);
For the lack of a message,
((BattleNotificationSender)
BattleResourceMediator.getMediatorInstance().getResource(
BattleParticipant.PROXY_PARTICIPANT,
BattleResource.BATTLE_NOTIFICATION_SENDER)).sendNotification(
((BattleNotificationBuilder)
(BattleResourceMediator.getMediatorInstance().getResource(
BattleOrganizer.getBattleParticipant(Battle.Participant.GOOD_GUYS),
BattleResource.BATTLE_NOTIFICATION_BUILDER))).buildNotification(
BattleOrganizer.getBattleState(BattleResult.BATTLE_LOST),
BattleManager.getChainOfCommand().getCommandChainNotifier()));
//...
// For A/B Testing
var getModalGreen = function() {
d = Math.random() * 100;
if ((d -= 99.5) < 0) return 1;
return 2;
};
🤦♀️
class ReturnValues {
private int numDays;
private String lastName;
public ReturnValues(int i, String s) {
numDays = i;
lastName = s;
}
public int getNumDays() { return numDays; }
public String getLastname() { return lastName; }
}
class AlsoReturnTransactionDate extends ReturnValues {
private Date txnDate;
public AlsoReturnTransactionDate(int i, String s, Date td) {
super(i,s);
txnDate = td;
}
public Date getTransactionDate() { return txnDate; }
}
class AddPriceToReturn extends AlsoReturnTransactionDate {
private BigDecimal price;
public AddPriceToReturn(int i, String s, Date td, BigDecimal px) {
super(i,s,td);
price = px;
}
public BigDecimal getPrice() { return price; }
}
class IncludeTransactionData extends AddPriceToReturn {
private Transaction txn;
public IncludeTransactionData(int i, String s, Date td, BigDecimal px, Transaction t) {
super(i,s,td,px);
txn = t;
}
public Transaction getTransaction() { return txn; }
}
class IncludeParentTransactionId extends IncludeTransactionData {
private long id;
public IncludeParentTransactionId(int i, String s, Date td, BigDecimal px, Transaction t, long id) {
super(i,s,td,px,t);
this.id = id;
}
public long getParentTransactionId() { return id; }
}
class ReturnWithRelatedData extends IncludeParentTransactionId {
private RelatedData rd;
public ReturnWithRelatedData(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd) {
super(i,s,td,px,t,id);
this.rd = rd;
}
public RelatedData getRelatedData() { return rd; }
}
class ReturnWithCalculatedFees extends ReturnWithRelatedData {
private BigDecimal calcedFees;
public ReturnWithCalculatedFees(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf) {
super(i,s,td,px,t,id,rd);
calcedFees = cf;
}
public BigDecimal getCalculatedFees() { return calcedFees; }
}
class ReturnWithExpiresDate extends ReturnWithCalculatedFees {
private Date expiresDate;
public ReturnWithExpiresDate(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed) {
super(i,s,td,px,t,id,rd,cf);
expiresDate = ed;
}
public Date getExpiresDate() { return expiresDate; }
}
class ReturnWithRegulatoryQuantities extends ReturnWithExpiresDate {
private RegulatoryQuantities regQty;
public ReturnWithRegulatoryQuantities(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed, RegulatoryQuantities rq) {
super(i,s,td,px,t,id,rd,cf,ed);
regQty = rq;
}
public RegulatoryQuantities getRegulatoryQuantities() { return regQty; }
}
class ReturnWithPriorities extends ReturnWithRegulatoryQuantities {
private Map<String,Double> priorities;
public ReturnWithPriorities(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed, RegulatoryQuantities rq, Map<String,Double> p) {
super(i,s,td,px,t,id,rd,cf,ed,rq);
priorities = p;
}
public Map<String,Double> getPriorities() { return priorities; }
}
class ReturnWithRegulatoryValues extends ReturnWithPriorities {
private Map<String,Double> regVals;
public ReturnWithRegulatoryValues(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed, RegulatoryQuantities rq, Map<String,Double> p, Map<String,Double> rv) {
super(i,s,td,px,t,id,rd,cf,ed,rq,p);
regVals = rv;
}
public Map<String,Double> getRegulatoryValues() { return regVals; }
}
🤦♀️
enum Bool
{
True,
False,
FileNotFound
};
🤦♀️
package com.xxxx.flow.dto;
import com.xxxx.DTOIn;
import com.xxxx.XipConectorDAO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IniciarProcesoContratacionCarterasAsesoradasSBPModel implements DTOIn {
private static final long serialVersionUID = -5565412435484318101L;
private XipConectorDAO XipConectorDAO;
private String carteraAsesorada;
private String idTestMifid;
private String propuestaInversion;
private String productosVinculados;
private String idCliente;
private String intervinientes;
}
🤦♀️
Una palabra por concepto
- Consistencia en toda la aplicación.
- Evita que una palabra tenga varios significados.
- Evita usar varias palabras para lo mismo.
¿Cómo decías que se recuperaba el id?
employee.id.Get()
dependent.GetId()
supervisor()
candidate.id()
Palabras del ámbito de negocio
- Consistencia en toda la aplicación.
- Evita que una palabra tenga varios significados.
- Evita usar varias palabras para lo mismo.
🤦♀️
Funciones
(Personalmente, prefiero FP a declarativa)
Funciones
- Primera regla: Tienen que ser muy pequeñas.
- Segunda regla: Deben ser aún más pequeñas.
- Tercera regla: Todavía es muy grande.
Funciones
Una función:
hace solo UNA cosa,
y la hace BIEN
y no hace nada más.
Funciones
Una función no debe mezclar
niveles de abstracción.
Por ejemplo, no debe hacer invocaciones
a funciones de alto nivel y cosas técnicas
a la vez.
String documento = generaDocumento();
documento.append("Generado automaticamente por el sistema.")
SWITCH
Suficiente entidad para ser la única responsabilidad de una función.
public Importe calculaPaga(Empleado e) throws TipoEmpleadoInvalidoException {
switch(e.type) {
case COMISIONISTA:
return calculaComisiones(e);
case ASALARIADO:
return calculaSalario(e);
case FREELANCE:
return calculaPagoPorHoras(e);
default:
throw new TipoEmpleadoInvalidoException(e);
}
}
Muchas alternativas
var calculadoras = {};
calculadoras.COMISIONISTA = calculaComisiones();
calculadoras.ASALARIADO = calculaSalario();
calculadoras.FREELANCE = calculaPagoPorHoras();
function calculaPaga(empleado) {
return calculadoras[empleado.type](empleado);
}
Y otra forma más ADT
function calculaPaga(Empleado e): Importe {
return isComisionista(e) ? calculaComisiones(e) :
isAsalariado(e) ? calculaSalario(e)
: calculaPagoPorHoras(e)
}
type Comisionista = ...
type Asalariado = ...
type Freelance = ...
type Empleado = Comisionista | Asalariado | Freelance;
function calculaComisiones(e: Comisionista): Importe{
//...
}
function calculaSalario(e: Asalariado): Importe{
//...
}
function calculaPagoPorHoras(e: Freelance): Importe{
//...
}
function isComionista(e:Empleado): e is Comisionista {
return e instanceOf Comisionista;
}
function isAsalariado(e:Empleado): e is Asalariado {
return e instanceOf Asalariado;
}
Nombre descriptivos
- Sin miedo a los nombres largos.
- Mejor un nombre largo y claro
que corto y enigmático. - Mejor un nombre largo y claro
que un comentario. - Utiliza un estilo que facilite la lectura.
- Dado lo anterior, mejor corto que largo.
miObj.elMetodoConNombreTanLargoQueAlFinalNoTeAcuerdasDelPrincipio()
Argumentos
Número de parámetros:
- Ninguno: bueno... ¿factoría?¿side effect warning?
- Uno (función monádica): BIEN, fácil de concatenar.
- Dos (función diádica): ok
- Tres (función triádica): hummmmmm..... ¿estas seguro?
- Cuatro: ¿estas seguro?
- Más de cuatro: Refactoriza
También puedes apoyarte en "named parameters" si el lenguaje lo permite
tf.nn.conv2d(
input,
filter,
strides,
padding,
use_cudnn_on_gpu=True,
data_format='NHWC',
dilations=[1, 1, 1, 1],
name=None
)
Argumentos Flag
-
Si es un argumento, igual lo puedes cambiar por dos funciones
- Más sencillo con named arguments (para argumentos opcionales)
void setOn();
void setOff();
//... vs
void setSwitch(boolean on);
book(aCustomer, true)
//... vs
book(aCustomer, isPremium:true)
If {commission_rate} = 1.2
Then 20
Else If {commission_rate} = 1.15
Then 15
Else If {commission_rate} = 1.0
Then 0
Else If {commission_rate} = 1.16
Then 16
Else If {commission_rate} = 1.125
Then 12.5
Else If {commission_rate} = 1.1
Then 10
Else {commission_rate}
End
🤦♀️
Sin efectos secundarios
- Mejor para programación funcional.
- Los efectos secundarios
generan acoplamientos. - Si no queda más remedio, que quede
explicitado:
.compruebaClaveEIniciaSesion()
Sin efectos secundarios: beware of the Date!
function sePuedeConsumir(producto) {
return new Date() < producto.fechaCaducidad;
}
/*comentarios*/
¿Buenos o malos?
/*comentarios*/
- Siempre dicen la verdad
- Nunca dicen la verdad
- A veces dicen la verdad
/*comentarios*/
TooMuchCopyPasteException
Son la primera víctima del:
// if hasSiblings, actionType == cont; else actionType == end.
if ($hasSiblings)
{
$actionType = "cont";
}
else
{
$actionType = "end";
}
🤦♀️
/*comentarios*/
De los buenos
- Comentarios con información legal
- Comentarios informativos (por ejemplo,
referenciando documentos normativos
sobre el cálculo de el DC de un CCC) - Explicando intenciones
- Comentando APIs públicas
/*comentarios*/
De los malos
- Mascullando
- Redundantes: RUIDOOOO
- Que conducen a engaño: TooMuchCopyPasteException
- Comentando código complicado: reescribe el código!!!
- Código comentado: ¿no tienes control de versiones?
- Diario: ¿no tienes control de versiones????
- Marcas personalizadas: porque el cierre del bucle está muy lejos?? ¿en serio?!?!
- Documentación de métodos privados???
/*comentarios*/
Crea una cultura de comentarios
FORMATEANDO
Algunos consejos sobre formateo vertical:
- Separación de bloques
- Bloques compactos
Distancia entre elementos:
- Acerca conceptos relacionados
- Una función que invoque a otra: acércalas
- El llamador por encima del llamado (excepción:
JavaScript que pasa JSLint) - Variables declaradas cerca de donde se usan
- Variables de instancia al principio de la clase
FORMATEANDO
TABs vs SPACEs
TABs vs SPACEs
"Developers Who Use Spaces Make More Money Than Those Who Use Tabs"
https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/
TABs vs SPACEs
TABs vs SPACEs
TABs vs SPACEs
Si pasas de tabuladores a espacios y vas a pedir un aumento de sueldo, te recomiendo que busques en Google la diferencia entre correlación y causalidad 🤪
FORMATEANDO
- Líneas cortas
- Una acción por línea
- Indentación, informa de jerarquía
FORMATEANDO
Team rules!
early optimization is the root of all evil
java
"Storing the data in an array saves cpu time"
🤦♀️
OBJETOS Y ESTRUCTURAS DE DATOS
Hay un motivo para la existencia de variables privadas:
no queremos que nadie dependan de ellas.
¿Por qué añadir getters y setters a todas ellas?
OBJETOS Y ESTRUCTURAS DE DATOS
Hay un motivo para la existencia de variables privadas:
no queremos que nadie dependan de ellas.
¿Por qué añadir getters y setters a todas ellas?
ABSTRACCIONES CORRECTAS
Un punto
public class Punto {
public double x;
public double y;
}
public interface Punto {
public double getX();
public double getY();
public setCartesianas(double x, double y);
public double getR();
public double getTheta();
public setPolares(double r, double theta);
}
LEY DE DEMETER
Un objeto no debe conocer nada
acerca de las interioridades de los objetos
que maneja.
Queremos evitar los accidentes de trenes:
ctx.getSession(true).getStorage().get("dato").value
Hablando de estructuras de datos
Buscaros un buen sistema de tipos
Gestión de errores
Programación síncrona:
Programación asíncrona:
- Patrón de continuación (NodeJS): primer argumento
- Promesas u Observables: función controladora de error
- Procedural: Excepciones
- Funcional: Either (mejor que Maybe)
Para Javeros y similares
¿Unchecked o checked exceptions?
¿Qué debe tener una excepción?
Las excepciones tienen que definirse
en función de las necesidades de los usuarios.
Tienen que proporcionar el contexto suficiente
para entender por que se lanzan.
¿Qué debe tener una excepción?
Las excepciones tienen que definirse
en función de las necesidades de los usuarios.
Tienen que proporcionar el contexto suficiente
para entender por que se lanzan.
NULL
- No pases nulos
- No devuelvas nulos
Inmutabilidad
¿Por qué es importante?
- Predecible
- Rendimiento
- Seguimiento de las mutaciones
Hablemos de Tests
TDD
- Escribe código de producción sólo cuando
tengas un test unitario que lo pruebe que falle. - Escribe el código de prueba mínimo y necesario
que haga que el código de producción falle. - Solo escribe el código de producción imprescindible
para que el test pase.
Sobre los tests
- Limpio
- Un concepto por test
- Tres pasos para un test:
- Prepara el escenario
- Ejecuta el código
- Comprueba el resultado
Test FIRST
F: FAST. Rápidos.
I: INDEPENDENT. Independientes entre si.
R: REPEATABLE Repetibles independientemente del entorno.
S: SELF VALIDATING Si o No. Pasa o no pasa. Resultado claro.
T:TIMELY Escritos secuencialmente, justo antes
de necesitarse.
En serio,
¡hazlos!
Clases
Todo lo anterior aplica:
- Las clases deben ser pequeñas.
- Tener una única responsabilidad.
- Cohesión entre sus métodos y propiedades.
- Deben ser entendibles, tener sentido
Diseño emergente
Acrónimos y frasecitas
-
SRP: single responsibility principle
-
KISS: Keep it simple and stupid, short, straightforward...
-
YAGNI: you ain't gonna need it
-
DRY and DIE: Don't repeat your self, duplication is evil
Consejo: RY then DRY -
Demeter Law: no hables con extraños
-
POLS: principle of least surprise
-
POGE: Principle of good enough
-
Brook's law: nueve mujeres no hacen un bebé en un mes
Consideraciones finales
Alejarse del teclado 5 minutos después de trabajar
Clean Code v2018
By Gonzalo Ruiz de Villa
Clean Code v2018
Charla de Clean Code
- 3,098