DEV Community

Cover image for Variables Globales en Golang, si o no?
Gino Luraschi
Gino Luraschi

Posted on

Variables Globales en Golang, si o no?

Introducción

Hace un tiempo me encontré en mi trabajo la utilización de variables globales, y aunque en este post vamos a hablar sobre la utilización en Golang, considero que esto se puede aplicar tranquilamente a cualquier lenguaje de programación.

Por que no?

La utilización de variables globales suele ser muy peligrosa y si no se utiliza con cuidado podemos llegar a tener ciertos efectos colaterales en nuestro código, algunas de las razones por las que debemos evitar esto es:

  • Difícil entender el programa: Al tener variables globales, podremos perder el hilo de nuestro programa, haciendo que este se vuelva inentendible. En particular por la siguiente razón.
  • Podemos cambiar la lógica en cualquier lugar de nuestro código, por cualquier razón: Al tener una variable global, debemos pensar en todos los usos y modificaciones que tendremos en el código productivo. Además, debemos considerar que un cambio que hagamos en una variable global se puede llegar a propagar por toda la aplicación, por lo que estamos acoplando piezas de nuestro código, lo cual causa el siguiente punto.
  • Reducción de modularidad y flexibilidad: Para este caso, tendremos en cuenta el ejemplo de que si varios módulos comparten una variable global, no podremos modificar la misma sin tener en cuenta los demás módulos. Lo cual hace perder flexibilidad si deseamos hacer nuestros módulos independientes.

Por que si?

Aunque se deben evitar estas variables, suelen ser útiles en ambientes en los que queremos declarar una constante que mejorará la lectura de nuestra aplicación, un ejemplo en la lib de go es time.go:

const (
    Layout      = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)
Enter fullscreen mode Exit fullscreen mode

En la lib de Go se exponen los distintos formatos de fecha preconfigurados, para permitirnos una mejor lectura del código y declaración de valores constante para toda la aplicación.

Forma de evitarlas: Pasando instancias desde un contenedor

Una forma de evitarla es tener una sola instancia e inyectarla al construir nuestros structs o clases, de esa forma tendremos controlado la utilización de las mismas, y nos dará la ventaja de poder mockearlas en caso de ser necesario, además en los tests, tendremos esa dependencia satisfecha.
Acá un ejemplo:

package db

import "github.com/go-pg/pg/v10"

var db *pg.DB // Nuestra variable global, la cual probablemente inicializamos en el mail.go

type Customer struct {
    ID       int
    Name     string
    LastName string
}

func SaveCustomer(cust Customer) error {
    // Al ejecutar nuestro test si no satisfacemos `db` esto va a fallar por un nil pointer
    _, err := db.Exec("insert into customers (id, name, lastname) values (%d, %s, %s)", cust.ID, cust.Name, cust.LastName)
    if err != nil {
        return err
    }
    return nil
}

// Un aproach es crear una estructura y poder inyectar la dependencia por constructor

type CustomerRepository struct {
    db *pg.DB
}

// NewCustomerRepository crea un repositorio y le inyecta
// la DB, por lo que en nuestro test
// si siempre usamos el constructor
// lograremos satisfacer esa dependecia
func NewCustomerRepository(db *pg.DB) CustomerRepository {
    return CustomerRepository{
        db: db,
    }
}

func (repo CustomerRepository) SaveCustomer(cust Customer) error {
    // En este caso, db de repo deberia estar satisfecha porque le inyectamos la DB en el constructor
    _, err := repo.db.Exec("insert into customers (id, name, lastname) values (%d, %s, %s)", cust.ID, cust.Name, cust.LastName)
    if err != nil {
        return err
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Otra forma de evitarlas: Patron Singleton

Una forma de evitar tener variables globales, es la utilización del patrón singleton, esta nos permite tener una instancia de una sola variable, la cual se inicializa sólo la primera vez, por lo tanto nos permite tener siempre la sola instancia en nuestra aplicación.
Un ejemplo de esto, puede ser la inicialización de una conexión a una DB:

package db

import "github.com/go-pg/pg/v10"

var db *pg.DB

func GetDB() *pg.DB {
    if db == nil {
        db = pg.Connect(&pg.Options{
            Addr:     "localhost:5432",
            User:     "postgres",
            Password: "password",
            Database: "testdb",
        })
        // outside we need defer db.Close()
    }
    return db
}
Enter fullscreen mode Exit fullscreen mode

Aca vemos como al hacer el GetDB si nuestra variable de instancia es nula, se inicializa la primera vez.

package main

import "postgres/db"

func main() {
    connection := db.GetDB() // Si es nula, se inicializa la DB
    _, err := connection.Exec("insert into customers(...) values (...)")
    if err != nil {
        panic(err.Error())
    }
    // ...
    connection := db.GetDB() // Es la misma conexión que arriba.
    _, err := connection.Exec("insert into products(...) values (...)")
    if err != nil {
        panic(err.Error())
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: Ambos script están en Gist de Github

Singleton, es un antipatrón?

En algún post futuro, prometo hablar por que se lo considera un antipatrón...

Conclusión

Como desarrolladores de software, estamos atentos a lo que vemos en nuestro día a día, acá revisamos porque las variables globales no nos ayudan mucho en el día a día, y aunque hay mejores formas de poder evitarlas, tenemos que considerar la mejor forma de hacerlo.
De paso, quiero recomendar que sigan (La regla del Boy Scout)[https://deviq.com/principles/boy-scout-rule] que en principio nos dice que debemos dejar el campo (código) en mejores condiciones del que lo recibimos. A veces cuesta, pero lo mejor es siempre poder hacer ajustes a medida que esté a nuestro alcance.

Top comments (0)