Code Smells en Lenguajes Dinámicos vs. Estáticos
Los lenguajes con tipado estático (Java, C#, TypeScript) y dinámico (JavaScript, Python, Ruby) presentan diferentes desafíos.
6.1. Smells Específicos de Lenguajes Dinámicos
- Abuso de Duck Typing: Asumir propiedades o métodos sin verificación
- Tipos Mixtos en Colecciones: Arrays/listas que contienen elementos de diferentes tipos
- Parámetros Mágicos: Funciones que cambian comportamiento según tipo de parámetro
- Monkey Patching Excesivo: Modificar objetos/clases en tiempo de ejecución
6.2. Ejemplo: Abuso de Duck Typing en Python
def process_data(data):
# Asume que data tiene cierta estructura sin verificarla
result = 0
for item in data:
result += item['value'] * 2 # Puede fallar si item no tiene 'value'
return result
# Podría fallar en tiempo de ejecución
process_data([{'value': 1}, {'count': 2}, {'value': 3}])
Refactorización
def process_data(data):
result = 0
for item in data:
# Verificar la existencia de la clave
if 'value' in item:
result += item['value'] * 2
else:
# Manejar el caso específicamente o loggear
print(f"Warning: Item {item} doesn't have 'value' property")
return result
# Mejor opción en Python moderno - usar typings
from typing import List, Dict, Any, TypedDict
class ValueItem(TypedDict):
value: int
def process_data_typed(data: List[ValueItem]) -> int:
return sum(item['value'] * 2 for item in data)
6.3. Smells Específicos de Lenguajes Estáticos
- Type Casting Excesivo: Convertir tipos constantemente
- Supresión de Advertencias: Ignorar advertencias del compilador/analizador
- Genericidad Compleja: Estructuras de tipos excesivamente complejas
- Reflection sin Necesidad: Uso de reflexión cuando existen alternativas estáticas
public class DataProcessor {
public void processItems(List items) { // Raw type
for (Object item : items) {
if (item instanceof String) {
String s = (String) item; // Cast explícito
processString(s);
} else if (item instanceof Integer) {
Integer i = (Integer) item; // Cast explícito
processInteger(i);
} else if (item instanceof Map) {
Map<String, Object> map = (Map<String, Object>) item; // Cast potencialmente inseguro
processMap(map);
}
}
}
private void processString(String s) { /* ... */ }
private void processInteger(Integer i) { /* ... */ }
private void processMap(Map<String, Object> map) { /* ... */ }
}
Refactorización
public class DataProcessor {
// Versión genérica type-safe
public <T> void processItems(List<T> items, ItemProcessor<T> processor) {
for (T item : items) {
processor.process(item);
}
}
// Interfaz para procesamiento
public interface ItemProcessor<T> {
void process(T item);
}
// Implementaciones específicas
public static class StringProcessor implements ItemProcessor<String> {
@Override
public void process(String item) { /* ... */ }
}
public static class IntegerProcessor implements ItemProcessor<Integer> {
@Override
public void process(Integer item) { /* ... */ }
}
public static class MapProcessor implements ItemProcessor<Map<String, Object>> {
@Override
public void process(Map<String, Object> item) { /* ... */ }
}
}
// Uso
DataProcessor processor = new DataProcessor();
List<String> strings = Arrays.asList("a", "b", "c");
processor.processItems(strings, new StringProcessor());