Tabla de Contenidos

Mejores Prácticas en PAE

Convenciones de código

Nomenclatura

// ✅ Bueno
 
// Services: servicio de negocio
class DeliveryService { }
class MachineService { }
 
// Managers: coordinadores
class StateManager { }
class P2PManager { }
 
// Emitters: publicadores de eventos
object StateNameEmitter { }
object DeliverySyncUiEmitter { }
 
// Repositories: acceso a datos
interface Repository { }
 
// ViewModel: state holder para UI
class LaboratoryViewModel { }
 
// Data classes: entidades
data class Delivery(...)
data class Beneficiary(...)
 
// Enums: conjuntos finitos
enum class P2PMachineStatus { Active, Inactive, Error }
 
// Companions: funciones/propiedades de clase
companion object {
    fun create(): StateManager { }
}
 
// ❌ Malo
class DeliveryLogic { }           // vago
class D { }                        // demasiado corto
class BeneficiaryRepoImpl { }      // sufijo "Impl"
val d = Delivery()                // variable de 1 letra

Paquetes

co.ada.paemachine/
├── screens/                       # Composables de pantalla
│   ├── LaboratoryScreen.kt
│   ├── ShiftSelectionScreen.kt
│   └── ...
├── viewmodels/                    # State holders
│   └── LaboratoryViewModel.kt
├── navigation/                    # Routing
│   └── AppNavigation.kt
├── di/                            # Dependency injection
│   └── AppModule.kt
└── ...

co.ada.domain/
├── state/                         # State machine
├── services/                      # Business logic
├── hardware/                      # Interfaces hardware
├── emitters/                      # Observable streams
└── ...

co.ada.data/
├── entities/                      # DB models
├── repository/                    # Data access
├── services/                      # CRUD helpers
└── ...

Archivos

Patrones y anti-patrones

✅ Patterns a usar

1. Repository Pattern

// ✅ Bueno: abstracción clara
interface Repository {
    val deliveries: Table<Delivery>
    fun getDelivery(id: Long): Delivery?
}
 
// Uso
val delivery = repository.getDelivery(1)

2. State Pattern

// ✅ Bueno: flujo predecible
interface State {
    suspend fun run(next: () -> Unit, retry: (String) -> Unit)
}
 
// Transiciones claras
WaitingForWeight → CaptureImages → CaptureFace

3. Dependency Injection (DI)

// ✅ Bueno: inyección en constructor
class StateManager(
    val repository: Repository,
    val hardware: Hardware
) { }
 
// Fácil testear
val testManager = StateManager(mockRepository, mockHardware)

4. Extension Functions

// ✅ Bueno: funciones de utilidad
fun String.toDeliveryId(): Long {
    return this.toLong()
}
 
fun Float.isValidWeight(): Boolean {
    return this > 0.5f && this < 100f
}
 
// Uso limpio
"123".toDeliveryId()
weight.isValidWeight()

5. Sealed Classes

// ✅ Bueno: tipos discriminados
sealed class SyncResult {
    data class Success(val count: Int) : SyncResult()
    data class Failure(val error: String) : SyncResult()
    object InProgress : SyncResult()
}
 
// Pattern matching exhaustivo
when (result) {
    is SyncResult.Success -> {}
    is SyncResult.Failure -> {}
    is SyncResult.InProgress -> {}
    // compiler fuerza todos los casos
}

❌ Anti-patterns a evitar

1. God Objects

// ❌ Malo: responsabilidades múltiples
class Machine {
    fun captureDelivery() { }
    fun syncWithRutaPAE() { }
    fun configureHardware() { }
    fun generateReports() { }
    // ... 50 métodos más
}
 
// ✅ Bueno: responsabilidad única
class StateManager {
    fun cycle(): StateResult { }  // solo orquesta estados
}
 
class P2PManager {
    fun syncDeliveries() { }      // solo P2P
}

2. Null hell

// ❌ Malo: nullable everywhere
fun getDelivery(id: Long): Delivery? {
    val repo = getRepository()
    if (repo == null) return null
    val delivery = repo.find(id)
    if (delivery == null) return null
    // ...
    return delivery
}
 
// ✅ Bueno: null-safety
fun getDelivery(id: Long): Delivery = repository.getDelivery(id)
    ?: throw IllegalArgumentException("Delivery not found: $id")
 
// o usar Optional pattern
sealed class OptDelivery {
    data class Found(val delivery: Delivery) : OptDelivery()
    object NotFound : OptDelivery()
}

3. Callback hell

// ❌ Malo: nesting profundo
repository.getDelivery(id) { delivery ->
    repository.getBeneficiary(delivery.beneficiaryId) { beneficiary ->
        repository.getShift(delivery.shiftId) { shift ->
            // ... lógica aquí
        }
    }
}
 
// ✅ Bueno: corrutinas
val delivery = repository.getDelivery(id)
val beneficiary = repository.getBeneficiary(delivery.beneficiaryId)
val shift = repository.getShift(delivery.shiftId)
// lógica clara

4. Tight coupling

// ❌ Malo: acoplamiento fuerte
class StateManager {
    private val db = Database.getInstance()  // hardcoded
    private val api = ApiClient.getInstance() // hardcoded
}
 
// ✅ Bueno: inyección
class StateManager(
    private val repository: Repository,
    private val api: ApiClient
) { }

5. Magic numbers/strings

// ❌ Malo
if (weight > 0.5f) { }           // qué es 0.5?
val timeout = 30000              // milliseconds?
 
// ✅ Bueno
const val MIN_VALID_WEIGHT = 0.5f
const val API_TIMEOUT_MS = 30000
 
if (weight > MIN_VALID_WEIGHT) { }
delay(API_TIMEOUT_MS)

Manejo de errores

Try-catch o Result?

// ✅ Para operaciones IO o fallibles
val result = try {
    api.syncDeliveries()
    Result.Success()
} catch (e: Exception) {
    Result.Failure(e.message)
}
 
// ✅ Para funciones suspending
suspend fun syncDeliveries(): Result<List<Delivery>> = try {
    Result.Success(repository.deliveries.getAll())
} catch (e: Exception) {
    Result.Failure(e)
}
 
// ✅ En corrutinas
try {
    val deliveries = repository.deliveries.getAll()
} catch (e: IOException) {
    Logger.e("SYNC", "Network error: ${e.message}")
} catch (e: Exception) {
    Logger.e("SYNC", "Unknown error: ${e.message}")
}

Logging de errores

// ✅ Bueno: contexto claro
try {
    camera.takeSelfie()
} catch (e: Exception) {
    Logger.e("CAPTURE", "Failed to capture selfie: ${e.message}", e)
    // incluir stack trace en logs
}
 
// ❌ Malo
try {
    camera.takeSelfie()
} catch (e: Exception) {
    println("Error!")  // no logger, no contexto
}

Concurrencia y Corrutinas

Estructura

// ✅ Bueno: scope claro
viewModelScope.launch {  // en ViewModel
    try {
        val result = async { repository.getDelivery(id) }
            .await()
        _state.value = State.Success(result)
    } catch (e: Exception) {
        _state.value = State.Error(e.message)
    }
}
 
// ❌ Malo: scope global
GlobalScope.launch {  // evitar siempre
    repository.getDelivery(id)
}
 
// ❌ Malo: no cancela
Thread {
    repository.getDelivery(id)  // no se cancela con UI cleanup
}.start()

Suspensión

// ✅ Bueno: suspend function
suspend fun getDelivery(id: Long): Delivery = withContext(Dispatchers.IO) {
    repository.getDelivery(id)
}
 
// ✅ Bueno: usar correct dispatcher
withContext(Dispatchers.Main) {  // UI updates
    _state.value = newState
}
 
withContext(Dispatchers.IO) {  // DB, network
    repository.save(entity)
}
 
withContext(Dispatchers.Default) {  // CPU-intensive
    embedding = generateEmbedding(image)
}

Performance

Evitar allocations innecesarias

// ❌ Malo: crea lista nueva cada llamada
fun getActiveDeliveries(): List<Delivery> {
    return repository.deliveries
        .filter { it.status == Status.Active }  // O(n)
        .map { it.copy(processed = true) }       // O(n) copies
}
 
// ✅ Bueno: operación única
fun getActiveDeliveries(): Sequence<Delivery> {
    return repository.deliveries
        .asSequence()
        .filter { it.status == Status.Active }
        .map { it.copy(processed = true) }
}

Memory leaks en listeners

// ❌ Malo: memory leak
scale.addEventListener(object : ScaleEventListener {
    override fun onWeightChanged(weight: Float) {
        // captura 'this' implícitamente
        deliveryService.handleWeight(weight)
    }
})
 
// ✅ Bueno: remover listener
scale.addEventListener(listener)
// ... después
scale.removeEventListener(listener)
 
// ✅ Bueno: usar WeakReference
class DeliveryScaleListener : ScaleEventListener {
    private weak var deliveryService: DeliveryService? = null
}

Testing

Estructura AAA

@Test
fun testCalcSimilarity() {
    // ARRANGE: setup
    val embedding1 = FloatArray(512) { 1f }
    val embedding2 = FloatArray(512) { 1f }
 
    // ACT: ejecutar
    val similarity = calculateSimilarity(embedding1, embedding2)
 
    // ASSERT: verificar
    assertEquals(1.0f, similarity, 0.01f)
}

Nombres descriptivos

// ❌ Malo
fun test1() { }
fun testStateManager() { }
 
// ✅ Bueno
fun testStateTransitionFromWaitingForWeightToCaptureFaceOnSuccess() { }
fun testBeneficiarySearchReturnsTopResultsWithHighestSimilarity() { }

Mock vs Fake

// ✅ Mock: verificar llamadas
val repository = mock<Repository>()
`when`(repository.getDelivery(1)).thenReturn(delivery)
verify(repository).getDelivery(1)
 
// ✅ Fake: implementación simplificada
class FakeRepository : Repository {
    override fun getDelivery(id: Long) = fakeDeliveries[id]
}

Documentación

Código auto-documenta

// ❌ Malo: requires comment
val w = 0.5f  // minimum weight in kg
 
// ✅ Bueno: código habla por sí solo
const val MINIMUM_VALID_WEIGHT_KG = 0.5f
val isValidWeight = weight >= MINIMUM_VALID_WEIGHT_KG

KDoc para APIs públicas

/**
 * Calculates the similarity between two facial embeddings.
 *
 * @param embedding1 First embedding (512 dimensions)
 * @param embedding2 Second embedding (512 dimensions)
 * @return Similarity score between 0.0 and 1.0
 * @throws IllegalArgumentException if embeddings have wrong size
 *
 * @sample
 * val sim = calculateSimilarity(emb1, emb2)
 * println(sim)  // 0.95
 */
fun calculateSimilarity(embedding1: FloatArray, embedding2: FloatArray): Float {
    require(embedding1.size == 512) { "embedding1 must have 512 dimensions" }
    require(embedding2.size == 512) { "embedding2 must have 512 dimensions" }
    // ...
}

Version Control

Commits

✅ Bueno:
commit a3f1e8c
    Add vectorial search to beneficiary lookup
    
    - Implement HNSW index in VectorDB
    - Add 512-dim embedding support
    - Result: 10ms queries (was 100ms)
    
    Fixes #123

❌ Malo:
commit a3f1e8c
    Fix stuff

Branches

✅ Esquema
feature/delivery-state-machine
feature/p2p-sync
bugfix/camera-permissions
hotfix/scale-calibration

❌ Malo
new-stuff
temporary
test123

Code review

Checklist

Security review

Performance review

Checklist pre-commit

# Format código
./gradlew ktlintFormat
 
# Lint
./gradlew lint
 
# Tests
./gradlew test
 
# Build
./gradlew build
 
# Code coverage
./gradlew jacocoTestReport
 
git commit -m "Meaningful message"