Mostrando las entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando las entradas con la etiqueta programación. Mostrar todas las entradas

08/04/2022

Structs y algunas notas

Los tipos de datos de toda la vida; enteros, cadenas de texto, booleanos, etc. funcionan muy bien para realizar tareas triviales, pero el desarrollo de software no siempre trata acerca de encontrar el mayor de dos números. Hay muchos problemas que resolver y muchas entidades que modelar.

Una struct es un tipo de datos definido por el usuario que agrupa un conjunto de campos que también tienen tipos definidos.

En efecto, gran parte de la solución de un problema, cuando programamos, pasa por representar de manera adecuada a una entidad del mundo real y a su flujo de vida. Pues bien, ¿De que forma representamos un automóvil y su flujo de fabricación? ¿Como modelamos una factura o un chocolate? y más allá, ¿Como modelamos dos chocolates de distintas marcas?

Cada lenguajes de programación provee su propia forma de lidiar con este problema, la cual se puede resumir en algún tipo de datos personalizado. Algunos ponen a nuestra disposición arreglos asociativos, otros clases, etc. Go por su parte nos deja utilizar structs, los cuales ya mencionabamos en un artículo anterior.

Una struct es un tipo de datos definido por el usuario (en el sentido de que quien programa es el usuario) que agrupa un conjunto de campos que también tienen tipos definidos.

Retomando el ejemplo del automóvil, si lo abstraemos, vemos que posee atributos cómo marca, modelo, año de fabricación, color, etc. Usemos este caso para ejemplificar la declaración de structs

Declaración de structs
  

type Automovil struct {
    Marca string
    Color string
    Ano int
}
  

playground

De esta forma declaramos una struct type conteniendo los campos Marca, Color y Ano cómo una estructura de datos personalizada llamada Automovil.


  
  
package main

import "fmt"

type Automovil struct {
	Marca string
	Color string
        Ano int
}

func(a Automovil) Acelerar() {
    fmt.Println("Este automóvil " + a.Marca + " de color " + a.Color + " esta acelerado")
}

func main() {
	myCar := Automovil{
		Marca: "Suzuki",
		Color: "Rojo",
                Ano: 1999,      // Notese la coma al final de la última línea
	}
          
	fmt.Println("Mi Auto: ", myCar)
 
} 
  

playground

¿Se dio cuenta de cómo definimos una variable de tipo struct Automovil justo al principio de la función Main? Esta forma de inicialización es conocida con el nombre de inicialización por struct literal. También podemos inicializar variables con nuestro struct con el estamento new


  
	var myCar *Automovil
	myCar = new(Automovil)
	myCar.Marca = "Suzuki"
	myCar.Color = "Rojo"
	myCar.Ano = 1999

	fmt.Println("Mi Auto: ", myCar)

  

playground

Asociando métodos a nuestro struct

En Go, las structs puede tener métodos asociados.


  
package main

import "fmt"

type Automovil struct {
	Marca string
	Color string
	Ano   int
}

func (a Automovil) Acelerar() {
	fmt.Println("Este automóvil " + a.Marca + " de color " + a.Color + " esta acelerado")
}

func main() {
	myCar := Automovil{
		Marca: "Suzuki",
		Color: "Rojo",
		Ano:   1999,
	}

	myCar.Acelerar()
}
  


playground

 

Structs, clases y herencia
Go usa la composición para no repetir la implementación de comportamientos, que muy resumidamente, es armar nuestros objetos a partir de otros

Ud podrá pensar; Si un struct tiene campos y también métodos ¿En que se diferencia de una clase? No se equivoque; son parecidos pero no iguales, ambos esconden ideas diferentes en su concepción aunque a nosotros cómo usuarios nos permiten hacer cosas parecidas.

La primera diferencia es que Go no implementa mecanismos de herencia, con esto, surge la segunda diferencia, el encapsulamiento en Go se gestiona a nivel de paquetes, donde un elemento es exportable o no según si su letra inicial es mayúscula o minúscula; con la consecuencia de la no existencia del ámbito protected ¡Porque no existe herencia!

Como no hay herencia, necesitamos una forma para no repetir la implementación de comportamiento. En Go se tomó la decisión de diseño de hacerlo por la vía de la composición, que muy resumidamente es armar nuestros objetos a partir de otros. Si Ud ha trabajado con React o Flutter, entonces este concepto no le será nuevo, pues este patrón forma parte intrínseca de ellos para la creación de componentes.

Un pequeña nota sobre composición sobre herencia

Hay una rica y variada discusión acerca de composición sobre herencia, la cual es muy grande y escapa de las pretenciones de este artículo. Si no ha escuchado sobre el asunto, baste decir que podemos pensar cuando hablamos de composición en una relación del tipo tiene un, esta formado por, mientras que cuando hablamos de herencia, nos referimos a una relación del tipo es un. Fuente: McConnell, Steve (2004). Code Complete: A Practical Handbook of Software Construction. Microsoft Press; 2nd edición.

También puede encontrar una jugosa discusión sobre el tema en este hilo de Stackoverflow.



Composición

Para los diseñadores de Go la decisión de optar por la composición como la forma idiomática de compartir comportamiento y estado es tan importante, que el lenguaje nos permite incrustar en un struct en otro. Ej.


  
package main

import "fmt"

// Automovil el struct base que incrustaremos
type Automovil struct {
	Marca string
	Color string
	Ano   int
}

func (a Automovil) Acelerar() {
	fmt.Println("Este automóvil " + a.Marca + " de color " + a.Color + " esta acelerado")
}

// Delorean Porqué si vas a construir una máquina del tiempo ¿por qué no hacerlo con estilo? Dr. Emmet Lathrop Brawn
type Delorean struct {
	Automovil
}

func (d Delorean) TimeSkip() {
	fmt.Println("Viajando al 5 de noviembre de 1955 ¿Esa es la granja de Peabody?")
}

func NewDelorean() Delorean {
	d := Delorean{}
	d.Marca = "Delorean"
	d.Color = "Plata"
	d.Ano = 1988
	return d
}

func main() {
    myCar := Automovil{
        Marca: "Suzuki",
        Color: "Rojo",
        Ano:   1999,
    }

    myCar.Acelerar()
    
    autoGenial := NewDelorean()
    autoGenial.Acelerar()
    autoGenial.TimeSkip()
}

  


playground

 

Esto nos abre la puerta a sofisticadas formas de modelar las entidades que son parte de nuestro modelo de negocio. Pero eso es conversaciónm para otro artículo.

16/03/2022

Punteros en Go


Si completó el Tour de Go, recordará que Go posee punteros, y que un puntero contiene la dirección de memoria en la que está contenido un valor.

Un puntero es simplemente una variable que contiene la dirección de memoria de otra variable

Si declaramos en un programa Go una variable de tipo int y otra variable como un puntero a int se vería así:


  
    package main
    
    func main() {
        var i int  = 42
        var p *int = &i
    }
  

Donde en la variable i almacenamos el número 42, mientras que en la variable p almacenamos la dirección de memoría donde está alojada la variable i


diagrama 1

¿Se fijó en el ampersand & antes de la i en la definición de la variable puntero? Su nombre es operador de referencia y es la forma de obtener el puntero a un elemento en Go.

Seguramente también observó el asterisco * en la declaración del tipo de la variable puntero p. Es el operador de dereferencia o puntero, indica que la variable contiene un puntero, y también sirve para obtener el dato almacenado en la dirección de memoria a la que el puntero apunta.


  
    package main
    
    import "fmt"
    
    func main() {
        var i int  = 42
        var p *int = &i
        
        fmt.Println(*p) // prints 42
        
    }
    
  
playground

new

Para nuestra conveniencia, existe otra forma de obtener un puntero. la función new, la cual toma un tipo como argumento y devuelve un puntero a él. La expresión new(T) crea una variable sin nombre de tipo T y devuelve su dirección, la cual, como indicamos, es un puntero a T, o, diciéndolo de otra forma, un valor de tipo *T



package main

import "fmt"

func main() {
    p := new(int) // p, de tipo *int, apunta a una variable anónima de tipo int
    fmt.Println(*p) // imprime 0
    *p = 2 // Asigna el valor 2 a la variable anónima
    fmt.Println(*p) // imprime 2
}


playground

Las variables anónimas creadas con new no son distintas a las variables comunes y silvestres de toda la vida, con la diferencia de que no es necesario darles un nombre


¿Y para qué?

Todo esto está muy bien, pero, ¿De qué me sirve? ¿Por qué no usar las variables como lo hemos hecho toda la vida si es más simple?

Los punteros nos ayudan entre otras cosas a acceder a elementos a los cuales normalmente no tendríamos accesos. Imaginemos que quiere escribir una función que intercambie el contenido de dos variables enteras. La primera aproximación podría ser algo como:


  
package main

import "fmt"

func DaSwap(i1 int, i2 int) {
	i1, i2 = i2, i1
}

func main() {
	var a int = 42
	var b int = 16

	DaSwap(a, b)

	fmt.Println(a, " ", b)
}
  

playground

¿Que imprime el programa anterior? Si esperaba que imprimiera 16 42 lamento decirle que espere sentado. Como el intercambio de las variables se hace en la función DaSwap, este se hizo sobre copias de los valores de las variables que le fueron pasadas como argumentos, por lo que estas no fueron afectadas. Si usted es un veterano de las trincheras, entonces de seguro ya está pensando en la frase paso de variables por valor.

Si quisiéramos ver que el intercambio se realice correctamente, nos vemos obligados a usar punteros:


  

package main

import "fmt"

func DaSwap(i1 *int, i2 *int) {
	*i1, *i2 = *i2, *i1
}

func main() {
	var a int = 42
	var b int = 16

	DaSwap(&a, &b)

	fmt.Println(a, " ", b)
}
  

playground

Si de verdad completó el Tour de Go, usted podría debatirme que no necesita punteros, pues podría escribir la función de tal manera que devuelva los valores como una tupla en orden inverso al pasado como argumento, ¡Y yo me vería forzado a darle la razón!

  
package main

import "fmt"

func DaSwap(i1 int, i2 int) (int, int) {
	return i2, i1
}

func main() {
	var a int = 42
	var b int = 16

	a, b = DaSwap(a, b)

	fmt.Println(a, " ", b)
}

  

playground

Entonces, otra vez ¿De que me sirven los punteros? En muchos lenguajes de programación los punteros se usan, a veces sin que nos demos cuenta, por ejemplo para implementar listas y tipos de datos especiales que requieren aumento o disminución dinámica de capacidad; y para manipular objetos y arreglos. También permiten pasar funciones como argumento a otras funciones, pues estando definido un bloque de código en una posición de memoria (o en una ubicación del stack), parece lógico poder acceder a él a través de una referencia a dicha posición de memoria.



  
# Pseudo código

function HolaSoyUnCallback() {
  # Realizo alguna acción
}

func f1(callback) {
  # Realizo alguna acción en f1
  callback()
  # Mas acciones en f1
} 
 
  


Específicamente en Go, entre otras cosas, los punteros nos dan acceso a una característica muy poderosa del lenguaje, sobre la cual se pueden construir muchas cosas muy bonitas; los structs



Resumiendo

  • Un puntero se llama así porqué sirve para apuntar
  • ¿Donde apunta? Apunta a una dirección de memoria

12/03/2022

De historias, errores y humildad

Hace muchos años, a finales de los noventas, trabajaba como guardia de seguridad cuidando el edificio de un centro de formación técnica que había quebrado. Este se encontraba en Moneda, entre Amunategui y San Martín. En su tejado pasé la noche del año nuevo del 1999 al 2000, mirando los fuegos artificiales de la Torre Entel comiéndome un pollo asado que me habían regalado unos transeúntes que iban a ver los fuegos.

En sus instalaciones se encontraban restos de materiales para la formación de técnicos en odontología, como modelos de maxilares con dientes de goma, maniquíes, y hasta un modelo a tamaño real de un cerebro hecho no se de que materiales, pero que parecía muy real.

Inevitablemente cometeremos errores, por lo que debemos estar abiertos y preparados a que aparezcan. Porqué la programación es una actividad humana, realizada por humanos, y no somos infalibles.

Sin embargo, lo que mas llamaba mi atención era la biblioteca del extinto centro. Un salón enorme repleto de libros tirados por el suelo y amontonados en viejos estantes conteniendo la sabiduría de épocas perdidas.

Era una delicia pasar las noches explorándola buscando que leer hasta encontrar un libro que llamase mi atención y tirarme cuan largo era en un viejo sillón después de poner un casete de Iron Maiden en una radio igual de vieja.

Fue en una de esas bonitas sesiones que encontré un libro, el cual en ese momento me puse a leer de puro intruso. El título era curioso; The psychology of computer programming, de Gerald Weinberg ¿Como se va a aplicar psicología a las computadoras? fue la primera idea que se me ocurrió cuando empecé a ojearlo. Después de unas paginas ya entendía que donde se hablaba de psicología, se refería a las personas que programaban, es decir, el autor veía la programación como una actividad humana y social, intentando entender como las personas crean software y se reunen para hacerlo, dentro de una organización; un ambiente donde se desempeñan.

Uno de sus capítulos trataba sobre aceptar los errores entendiendo que estos no significan un menoscabo a la persona del programador; pues es muy distinto decir encontré errores en mi código, que decir encontré errores en el código que escribí. Lo que trae a colación el concepto de la propiedad del código. Los programadores no son dueños del código que escriben en el mismo sentido que los pintores son dueños de la obra que pintan, aunque la pinten para otros. Ejemplificando, Leonardo siempre estará asociado al cuadro de La Gioconda, pero los programadores que participan en un equipo escribiendo código para un proyecto no tienen propiedad sobre este y lo mas seguro es que nadie los recordará cuando se hayan ido del equipo y hayan pasado algunos años.

Ahora, muchos años después de esos eventos, y siendo yo mismo después de muchas vueltas de la vida un programador que comete muchos errores, recordar esa lectura me hace pensar en los problemas que enfrentaban los programadores del pasado, y en como estos problemas no han cambiado tanto aunque hayan pasado hartos años desde que se publicara el libro (1971) Es curioso como aunque en este tiempo han aparecido cientos de lenguajes, las máquinas han aumentado exponencialmente su potencia, y se hayan descubierto nuevos e interesantes patrones, los problemas que enfrentan las personas al programar siguen siendo, citando al autor:

  • Instrospección sin validación
  • Observaciones sobre una base muy estrecha
  • Observar las variables equivocadas, o fallar en detactar las correctas
  • Interferir con el fenomeno observado
  • Generar muchos datos y poca información
  • No fijarse en efectos grupales y comportamientros de grupo
  • Medir solo lo simple de medir
  • Transferir resultados a situaciones inaplicables

¿Le suenan conocidas algunas de estas proposiciones? Desde que trabajo como programador las veo una y otra vez, cayendo en ellas. Me hacen sentir una cierta hermandad con el viejo investigador que las propusiera.

Después de meditar, la conclusión a la que llego trata sobre la humildad que deberíamos adoptar quienes escribimos código. Inevitablemente cometeremos errores, por lo que debemos estar abiertos y preparados a que aparezcan. Porqué la programación es una actividad humana, realizada por humanos, y no somos infalibles.

Dejo el enlace a Google books y a amazon de The Psychology of Computer Programming para quien pudiera interesarse.

Usted no necesita un curso de Go


Al menos no al principio de su viaje de aprendizaje.
Como con toda tecnología seria, puede aprender las bases en un fin de semana, pero necesitará una vida de esfuerzo y aprendizaje continuo para dominarla.

Si escribimos en el buscador de internet preferido de Pedro, Juan y Diego la frase Go course, aparecen de inmediato una miríada de resultados donde se ofrecen talleres, especializaciones, guías completas de cero a héroe; tanto gratuitas como de pago; prometiendo desde introducirlo en el, hasta convertirlo en un maestro en este lenguaje. No se ustedes pero yo desconfío de forma natural en cualquier cosa que prometa convertirme en ninja-wizard-valar-ponga aquí su término preferido para denotar maestría técnica, en un par de horas.

La verdad no necesita pagar para aprender Go, sus mantenedores y comunidad han preparado todo lo que necesita para conseguir una competencia técnica básica, y escribo básica porqué como con toda tecnología seria, puede aprender las bases en un fin de semana, pero necesitará una vida de esfuerzo y aprendizaje continuo para sentirse seguro y competente al usarla.

Una vez que domine las bases del lenguaje, por supuesto necesitará recursos para aprender y profundizar en técnicas avanzadas e implementaciones de ideas. Es posible que necesite un buen libro sobre construir micro servicios con Go, otro sobre como escribir apis, o sobre como implementar un repositorio para desacoplar sus fuentes de datos. Pero si lo piensa, estos recursos que sin duda necesitará tratan más sobre una tecnología o patrón que sobre el lenguaje en sí mismo.

Así que no tire su dinero en cursos que le venden algo que es gratuito y ya está a su disposición. Y si quiere saberlo, lo único que necesita para comenzar su viaje con Go es un computador conectado a internet, un navegador decente, ir al sitio del Tour de Go y seguir el tutorial interactivo que le enseñará desde declarar variables hasta como manejar concurrencia.

En serio, insisto de la forma más respetuosa posible. ¿Quiere aprender Go? No gaste dinero en cursos y abra en su navegador la página del Tour.