De algoritmos a programas

Presentacion de la materia e introduccion a Kotlin

Clase teorica 1 · DCP

Nuestro punto de partida

Programar no es solamente escribir instrucciones que la computadora pueda ejecutar.

Durante la materia aprenderemos a:

  • Modelar un problema antes de implementar una solucion;
  • Distribuir datos y comportamiento de manera responsable;
  • Controlar estado, dependencias y errores;
  • Producir codigo que otras personas puedan leer, probar y cambiar.

El objetivo no es solo que el programa funcione hoy, sino que siga siendo comprensible mañana.

Recorrido de esta primera clase

  1. Acuerdos de cursada y uso responsable de IA.
  2. Diferencia entre algoritmo y programa.
  3. Datos, comportamiento, responsabilidades y estado.
  4. Complejidad accidental y limites del enfoque imperativo puro.
  5. Primer contraste entre Java y Kotlin.

La progresion sera problema → modelo → diseño → lenguaje.

IA: asistencia, no autoria invisible

La IA generativa puede servir para explorar alternativas, explicar conceptos, revisar codigo o proponer casos de prueba.

Su uso exige:

  • Declarar que herramienta se utilizo y para que;
  • Conservar los prompts e interacciones relevantes en el Prompt Log;
  • Contrastar las respuestas con documentacion, ejecuciones y pruebas;
  • Identificar que partes fueron adaptadas o descartadas;
  • Asumir responsabilidad por todo lo presentado.

Del problema a la ejecucion

Algoritmo y programa no son sinonimos

Algoritmo: una estrategia abstracta

Un algoritmo es una secuencia finita, precisa y ordenada de pasos que, a partir de ciertas entradas, produce una salida para resolver una clase de problemas.

Debe poder discutirse sin depender necesariamente de Kotlin, Java o una computadora particular.

Ejemplo: buscar el menor precio.

  1. Tomar el primer precio como minimo provisional.
  2. Compararlo con cada precio restante.
  3. Reemplazar el minimo cuando aparezca uno menor.
  4. Informar el minimo encontrado.

Programa: una solucion situada

Un programa es la expresion concreta de uno o mas algoritmos y reglas de negocio en un lenguaje de programacion, preparada para ejecutarse dentro de un entorno determinado.

Ademas de la estrategia, debe resolver decisiones reales:

  • tipos y representacion de los datos;
  • entrada, salida y persistencia;
  • errores, valores ausentes y casos limite;
  • uso de memoria, tiempo y otros recursos;
  • integracion con usuarios, archivos, redes o servicios.

Un mismo algoritmo, programas diferentes

Problema: calcular el promedio de calificaciones.

El algoritmo puede ser el mismo:

si no hay calificaciones, informar que no existe promedio
sumar todas las calificaciones
dividir la suma por la cantidad

Pero un programa debe decidir:

  • ¿entero o decimal?, ¿que precision?
  • ¿lista en memoria, archivo o base de datos?
  • ¿que ocurre con datos invalidos o ausentes?
  • ¿como se comunica el resultado?

La implementacion agrega contexto y responsabilidades.

Datos y comportamiento

Los datos representan el estado relevante del dominio. El comportamiento expresa operaciones y reglas que consultan o modifican ese estado.

data class Cuenta(val titular: String, private var saldo: Double) {
    fun extraer(monto: Double) {
        require(monto > 0 && monto <= saldo)
        saldo -= monto
    }
}

La cuenta no es solo dos valores: tambien protege la regla que determina cuando una extraccion es valida.

Pregunta de diseño: ¿que objeto posee la informacion necesaria para garantizar cada regla?

Responsabilidades: quien sabe y quien hace

Una responsabilidad es una obligacion de un componente: conocer cierta informacion, realizar una tarea o coordinar a otros componentes.

Una distribucion clara busca:

  • alta cohesion: cada componente concentra tareas relacionadas;
  • bajo acoplamiento: conoce lo minimo necesario de otros componentes;
  • reglas cerca de los datos que necesitan;
  • coordinadores que coordinan, sin absorber toda la logica.

Si una clase valida usuarios, calcula precios, guarda archivos y envia correos, tiene demasiados motivos para cambiar.

Estado: memoria del programa

El estado es el conjunto de valores observables que describen a un sistema u objeto en un instante determinado.

Una operacion puede:

  • consultar el estado sin modificarlo;
  • producir un nuevo valor;
  • realizar una transicion desde un estado valido hacia otro.
Pedido BORRADOR --confirmar()--> CONFIRMADO
Pedido CONFIRMADO --enviar()----> ENVIADO

No toda transicion debe permitirse: las reglas preservan los invariantes, condiciones que deben mantenerse siempre verdaderas.

Mutabilidad: el costo de cambiar en el lugar

var total = 0
for (precio in precios) {
    total += precio
}

Para comprender el resultado hay que seguir cada cambio de total en orden.

val total = precios.sum()

La segunda version expresa que se obtiene y reduce el estado mutable visible.

  • var: la referencia puede cambiar.
  • val: la referencia se asigna una sola vez.

Preferir inmutabilidad disminuye estados posibles y facilita razonar, probar y ejecutar en concurrencia.

Complejidad esencial y accidental

La complejidad esencial proviene del problema que debemos resolver; la complejidad accidental surge de las herramientas, estructuras o decisiones usadas para construir la solucion.

Ejemplo: calcular una tarifa con descuentos puede ser esencialmente complejo.

Son dificultades accidentales:

  • variables globales que pueden cambiar desde cualquier lugar;
  • duplicacion de reglas en varios modulos;
  • null sin tratamiento explicito;
  • detalles de infraestructura mezclados con logica de negocio;
  • secuencias de pasos cuyo orden correcto solo conoce su autor.

El diseño no elimina la complejidad esencial: evita agregar complejidad innecesaria.

Limites del enfoque imperativo puro

El estilo imperativo describe como alcanzar el resultado mediante comandos y cambios de estado.

Es adecuado para muchos algoritmos, pero llevado a todo el sistema puede generar:

  • estado compartido dificil de rastrear;
  • dependencia excesiva del orden de ejecucion;
  • funciones extensas con responsabilidades mezcladas;
  • efectos secundarios ocultos;
  • pruebas que requieren preparar demasiado contexto.

No se trata de eliminar instrucciones, sino de combinarlas con abstracciones que expresen intencion, dominio y contratos.

De instrucciones a modelos

Ante un problema, podemos preguntar:

Mirada centrada en pasos Mirada centrada en el modelo
¿que instruccion sigue? ¿que conceptos existen?
¿que variable modifico? ¿quien es responsable?
¿en que orden llamo funciones? ¿que transiciones son validas?
¿como proceso todos los casos? ¿que invariantes deben cumplirse?

La orientacion a objetos aporta una forma de organizar estado y comportamiento alrededor de entidades con responsabilidades claras.

Kotlin y Java

Mismo ecosistema, distinta expresividad

¿Por que compararlos?

Kotlin y Java:

  • son lenguajes de tipado estatico;
  • compilan para la JVM y comparten su ecosistema;
  • pueden interoperar dentro del mismo proyecto;
  • permiten programacion orientada a objetos;
  • cuentan con recoleccion automatica de memoria.

Kotlin busca conservar compatibilidad con Java y, al mismo tiempo, ofrecer sintaxis mas concisa, null-safety en el sistema de tipos y construcciones modernas.

Menos lineas no garantizan mejor diseño, pero menos ceremonia puede hacer mas visible la intencion.

Sintaxis: expresar la misma intencion

Java

public String saludo(String nombre) {
    return "Hola, " + nombre;
}

Kotlin

fun saludo(nombre: String): String = "Hola, $nombre"
  • El tipo se escribe despues del nombre; fun declara funciones.
  • La inferencia evita repetir tipos cuando son evidentes.
  • Las expresiones pueden devolver valores directamente.

Variables y tipos

Java

final String materia = "DCP";
int clase = 1;

Kotlin

val materia = "DCP"  // referencia de solo lectura
var clase = 1        // referencia reasignable

Variables y tipos (cont.)

Kotlin infiere String e Int, pero sigue siendo estaticamente tipado.

val anio: Int = 2026
// anio = "dos mil veintiseis"  // error de compilacion

Usaremos val por defecto y var solo cuando el cambio sea parte necesaria del modelo.

Null-safety: ausencia explicita

En Java, una variable de referencia puede contener null; un acceso incorrecto falla en ejecucion con NullPointerException.

String nombre = buscarNombre();
int largo = nombre.length();

En Kotlin, los tipos no admiten null por defecto:

var nombre: String = "Ada"
// nombre = null             // error de compilacion

var apodo: String? = null   // ausencia permitida

El ? convierte la posibilidad de ausencia en parte visible del contrato.

Trabajar con valores nullable

val apodo: String? = buscarApodo()

val largo = apodo?.length       // Int?
val visible = apodo ?: "Sin apodo"
  • ?. ejecuta el acceso solo si el receptor no es null.
  • ?: provee un valor alternativo, operador Elvis.
  • if (apodo != null) permite un acceso seguro dentro de la rama.
  • !! afirma que no es null, pero puede lanzar una excepcion.

Usar !! traslada nuevamente el riesgo al tiempo de ejecucion. Debe ser excepcional, no una salida rapida.

Clases para representar datos

Una clase Java suele requerir campos, constructor, getters, equals, hashCode y toString.

public final class Alumno {
    private final String legajo;
    private final String nombre;

    public Alumno(String legajo, String nombre) {
        this.legajo = legajo;
        this.nombre = nombre;
    }
    // getters, equals, hashCode, toString...
}

Gran parte de ese codigo es ceremonia tecnica, no una regla del dominio.

Data classes en Kotlin

Una data class es una clase cuyo proposito principal es almacenar datos; Kotlin genera automaticamente operaciones estructurales como equals, hashCode, toString, componentN y copy.

data class Alumno(
    val legajo: String,
    val nombre: String,
)

val ana = Alumno("A-104", "Ana")
val copia = ana.copy(nombre = "Ana Maria")

La igualdad compara los valores declarados en el constructor primario, no solamente la identidad de los objetos.

Data class no significa modelo anemico

Una data class tambien puede validar y ofrecer comportamiento:

data class Calificacion(val valor: Int) {
    init {
        require(valor in 1..10) { "Calificacion fuera de rango" }
    }

    fun estaAprobada(): Boolean = valor >= 4
}
  • El constructor rechaza estados invalidos.
  • La regla de aprobacion vive junto al dato que necesita.
  • El tipo Calificacion comunica mas que un Int suelto.

La concision del lenguaje debe ayudarnos a construir un modelo mas claro, no solo archivos mas cortos.

Comparacion inicial

Aspecto Java Kotlin
Declaraciones mas explicitas y ceremoniales inferencia y sintaxis concisa
Mutabilidad convencion + final distincion directa val / var
Ausencia referencias potencialmente null T y T? son tipos distintos
Objetos de datos metodos repetitivos o record data class y copy
JVM lenguaje historico principal interoperabilidad con Java

Kotlin reduce parte de la complejidad accidental, pero las buenas responsabilidades y abstracciones siguen siendo decisiones de diseño.

Ideas para llevarse hoy

  1. Un algoritmo describe una estrategia; un programa la vuelve ejecutable en un contexto real.
  2. Diseñar implica decidir donde viven los datos, el comportamiento y las responsabilidades.
  3. El estado mutable y los efectos ocultos aumentan la dificultad de comprender un sistema.
  4. Kotlin hace explicitas decisiones como mutabilidad y ausencia de valores.
  5. data class reduce ceremonia, pero no reemplaza el modelado.
  6. El uso de IA debe ser declarado, trazable, verificado y defendible.

Fuentes y lecturas recomendadas