Golang – 11. Pacotes e Documentação no Go

Este artigo é a parte 11 de 11 na série Golang

Neste artigo será apresentado a forma de se escrever pacotes em Go e como adicionar documentação ao seu código. Para isto será considerado a criação de um aplicativo, uma calculadora científica, a qual terá duas bibliotecas internas que podem ser usadas tanto pela calculadora como por outros aplicativos como uma biblioteca externa.

Na sequência será mostrado como adicionar documentação a este projeto, empregando regras simples e incorporando o documento ao desenvolvimento do próprio código, de forma simples e prática.

Pacotes e Documentação

O local adequado para o desenvolvimento de seus códigos em Go (seja uma biblioteca ou um aplicativo) é em alguma pasta dentro de $GOPATH/src. Não que seja impossível desenvolver código em outras pastas, no entanto seções do código desenvolvido em outras subpastas não serão facilmente importadas para o código na pasta raiz, como ocorre no $GOPATH/src.

Para este texto vou considerar o desenvolvimento de um aplicativo de uma “calculadora” com base na pasta $GOPATH/src/mycodes/calc. A intenção não é desenvolver uma calculadora por completo, mas implementar algum código pertinente para ilustrar uma possível forma de como organizar seu código e adicionar a documentação.

Para este projeto o diretório calc, do aplicativo, possui dois subdiretórios cmath, para por o suporte a vetor, matriz e outros tipos numéricos, e cnumeric para adicionar funções para resolução de raízes de polinômios, ajustes de funções e outras.

A estrutura de diretórios e os respectivos códigos ficam estruturados no diretório calc como segue:

Bash

alves@arabel:.../calc$ tree .
.
├── calc.go
├── cmath
│ ├── matrix.go
│ └── vectors.go
└── cnumeric
├── linear.go
└── poly.go

2 directories, 5 files


O diretório cmath contém dois arquivos: matrix.go e o vectors.go. O primeiro contém tipos, métodos e funções para o trabalho com matrizes e o segundo possui o mesmo para trabalhar com vetores.

O diretório cnumeric contém os arquivos linear.go e poly.go. No momento o linear contém uma função para ajuste linear e algumas funções estatísticas, e o poly.go possui apenas a função para extrair as raízes de um polinômio de segundo grau.

No diretório raiz do projeto coloquei o main.go, o que seria a calculadora em si. No momento este arquivo contém apenas alguns testes de algumas das funções dos demais arquivos e, num momento posterior, estes testes serão colocados em arquivo de testes apropriados.

Alguns programadores preferem usar um arquivo main.go em um diretório main, na raiz de calc. Para este aplicativo achai a escolha de um arquivo calc.go na raiz do projeto mais adequado, mas fica a seu gosto.

Como pode observar a estrutura e a organização dos arquivos ficam a critério do programador podendo dispor de diversas formas, mas existem algumas restrições que serão apresentadas a seguir.

Nomeando os Pacotes

No Go cada diretório é entendido como um pacote e pode conter apenas arquivos com a mesma declaração package no início do arquivo.go, ou seja, no diretório cmath possui os arquivos matrix.go e o vector.go, e ambos possuem a declaração “pacakge cmath” no início do código. Portanto qualquer arquivo colocado nesta pasta tem de possuir a mesma declaração “package cmath“, ou seja, pertencem ao pacote cmath.

É de praxe nomear o pacote com o mesmo nome do diretório em que o código se encontra, embora isto não é uma regra. Observe que em todos os artigos anteriores os códigos apresentados receberam a declaração “package main“, independente do nome do diretório em que se encontravam.

Neste projeto os arquivos foram arrumados em dois subdiretórios: cmath e cnumeric. Da perspectiva do Go estes dois subdiretórios são vistos como dois pacotes internos do projeto calc que serão nomeados como os pacotes cmath e cnumeric.

Pacote cmath

O pacote cmath possui dois os arquivos vectors.go e matrix.go, e para declarar que ambos pertencem ao mesmo pacote é necessário adicionar o comando “package cmath” no início de cada arquivo.

Antes de prosseguir com a discussão sobre pacotes é feito uma breve apresentação dos conteúdos dos dois códigos do pacote cmath, iniciando pelo vectors.go:

package cmath

import (
	"fmt"
	"math"
)

type Vector struct {
	X float64
	Y float64
	Z float64
}

func (v Vector) Norm() float64 {
	return math.Sqrt(math.Pow(v.X, 2) + math.Pow(v.Y, 2) + math.Pow(v.Z, 2))
}

func (v Vector) Add(w Vector) Vector {
	return Vector{v.X + w.X, v.Y + w.Y, v.Z + w.Z}
}

func (v Vector) Sub(w Vector) Vector {
	return Vector{v.X - w.X, v.Y - w.Y, v.Z - w.Z}
}

func (v Vector) RealProd(r float64) Vector {
	return Vector{v.X * r, v.Y * r, v.Z * r}
}

func (v Vector) DotProd(w Vector) float64 {
	return v.X*w.X + v.Y*w.Y + v.Z*w.Z
}

func (v Vector) CrossProd(w Vector) Vector {
	return Vector{
		v.Y*w.Z - v.Z*w.Y,
		v.Z*w.X - v.X*w.Z,
		v.X*w.Y - v.Y*w.X,
	}
}

func (v Vector) String() string {
	return fmt.Sprintf("(%3.2fi + %3.2fj + %3.2fk)", v.X, v.Y, v.Z)
}

Na primeiro linha de código é declarado que este arquivo pertence ao pacote cmath, o mesmo nome do subdiretório em que se encontra.

Em vectors.go está definido o tipo Vector e seus métodos, como o módulo (Norm), adição e subtração (métodos Add e Sub), produto por um um real, escalar e vetorial (métodos RealProd, DotProd e CrossProd respectivamente). Por fim é definido a saída String() para impressão formatada do vetor. Este último método coloca o tipo Vector como parte da interface Stringer, sendo invocado durante os fmt.Prints.

Este pacote importa da biblioteca padrão fmt, para a impressão e formatação, e o math, empregado na linha 15 para extrair a raiz quadrada no cálculo do módulo.

Já o arquivo matrix.go traz o tipo Matrix e diversos métodos para manipular matrizes.

package cmath

import (
	"fmt"
)

type Matrix struct {
	elements [][]float64
	rows     int
	cols     int
}

func (m *Matrix) setRowsCols() {
	if m.rows != len(m.elements) {
		m.rows = len(m.elements)
	}
	if m.cols != len(m.elements[0]) {
		m.cols = len(m.elements[0])
	}
}

func (m Matrix) GetElement(row, col int) (float64, error) {
	if row >= 0 && row < m.rows && col >= 0 && col < m.cols {
		return m.elements[row][col], nil
	}
	return 0., fmt.Errorf("there is not element [%d][%d] in this matrix", row, col)
}

func (m *Matrix) SetElement(row, col int, v float64) error {
	if row >= 0 && row < m.rows && col >= 0 && col < m.cols {
		m.elements[row][col] = v
		return nil
	}
	return fmt.Errorf("there is not element [%d][%d] in this matrix", row, col)
}

func (m *Matrix) StartMatrix(r int, c int, e ...float64) error {
	if r*c != len(e) {
		return fmt.Errorf("rows (%d) x columns (%d) is different from the number of matrix elements (%d)", r, c, len(e))
	}

	for i := 0; i < r; i++ {
		row := []float64{}
		for j := 0; j < c; j++ {
			row = append(row, e[i*c+j])
		}
		m.elements = append(m.elements, row)
	}
	m.setRowsCols()

	return nil
}

func (m *Matrix) ZerosMatrix(r, c int) {
	for i := 0; i < r; i++ {
		row := make([]float64, c)
		m.elements = append(m.elements, row)
	}

	m.setRowsCols()
}

func (m Matrix) String() string {
	str := "["
	for r := 0; r < m.rows; r++ {
		for c := 0; c < m.cols; c++ {
			str += fmt.Sprintf("%+3.2f ", m.elements[r][c])
		}
		str = str[:len(str)-1] + "]\n["
	}
	str = str[:len(str)-1]

	return str
}

func (m Matrix) Product(w Matrix) (Matrix, error) {
	if m.cols != w.rows {
		return Matrix{}, fmt.Errorf("in A*B the A.columns must be equal to B.rows")
	}

	z := Matrix{}
	z.ZerosMatrix(m.rows, w.cols)

	for i := 0; i < z.rows; i++ {
		for j := 0; j < z.cols; j++ {
			for k := 0; k < z.rows; k++ {
				z.elements[i][j] += m.elements[i][k] * w.elements[k][j]
			}
		}
	}

	return z, nil
}

func (m Matrix) Compare(w Matrix) bool {
	if m.cols != w.cols || m.rows != w.rows {
		return false
	}

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			if m.elements[i][j] != w.elements[i][j] {
				return false
			}
		}
	}

	return true
}

func (m Matrix) RealProduct(c float64) Matrix {
	w := Matrix{}
	w.ZerosMatrix(m.rows, m.cols)

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			w.elements[i][j] = m.elements[i][j] * c
		}
	}

	return w
}

func (m Matrix) Add(w Matrix) (Matrix, error) {
	if m.rows != w.rows || m.cols != w.cols {
		return Matrix{}, fmt.Errorf("it is not possible to add matrices of different sizes")
	}
	z := Matrix{}
	z.ZerosMatrix(m.rows, m.cols)

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			z.elements[i][j] = m.elements[i][j] + w.elements[i][j]
		}
	}

	return z, nil
}

func (m Matrix) Sub(w Matrix) (Matrix, error) {
	if m.rows != w.rows || m.cols != w.cols {
		return Matrix{}, fmt.Errorf("it is not possible to subtract matrices of different sizes")
	}
	z := Matrix{}
	z.ZerosMatrix(m.rows, m.cols)

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			z.elements[i][j] = m.elements[i][j] - w.elements[i][j]
		}
	}

	return z, nil
}

func (m Matrix) Det2() (float64, error) {
	if m.rows != 2 || m.cols != 2 {
		return 0., fmt.Errorf("is not a 2x2 matrix")
	}

	e := m.elements
	d := e[0][0]*e[1][1] - e[0][1]*e[1][0]

	return d, nil
}

func (m Matrix) Det3() (float64, error) {
	if m.rows != 3 || m.cols != 3 {
		return 0., fmt.Errorf("is not a 3x3 matrix")
	}
	e := m.elements
	d := e[0][0]*(e[1][1]*e[2][2]-e[1][2]*e[2][1]) -
		e[0][1]*(e[1][0]*e[2][2]-e[1][2]*e[2][0]) +
		e[0][2]*(e[1][0]*e[2][1]-e[1][1]*e[2][0])

	return d, nil
}

Neste código o tipo Matrix foi implementado com os atributos:

elements [][]float64
rows     int
cols     int

Observe que todos os atributos estão iniciados com letra minúsculas e isto significa que estes podem ser acessados diretamente apenas de dentro de arquivos do pacote cmath, ou seja dos arquivos matrix.go e vectors.go. Para manipular os elementos da matriz é necessário invocar os métodos GetElement e SetElement.

Outro fato importante é que tanto o matrix.go como o vectors.go possuem a mesma declaração package math. Isto significa que qualquer chamada de tipo ou funções entre estes dois arquivos não necessitam de um import, pois são o mesmo pacote.

No Go todos os arquivos “.go” em um diretório devem pertencer ao mesmo pacote, ou seja, possuir a mesma declaração package. Se tentar criar um outro “arquivo.go” na pasta cmath com uma declaração package diferente de cmath irá gera um erro na compilação.

É possível usar apenas math como nome do pacote ao invés de cmath, mas isto geraria um conflito com o math da biblioteca padrão. Isto pode ser contornado empregando um alias para o pacote local ou para a biblioteca padrão na declaração do import, o qual é mais aconselhável evitar.

Pacote cnumeric

O pacote cnumeric também possui dois os arquivos: linear.go e poly.go, e para declarar que ambos pertencem ao mesmo pacote é necessário adicionar o comando “package cnumeric” no início de cada arquivo, como feito anteriormente.

O arquivo linear.go possui funções para ajuste de retas (LinearAdj), algumas funções interna (iniciadas com letra minúsculas) e ao final algumas funções de médias para estatística: ArithmeticMean, QuadraticMean, MidRange, Max e Min.

package cnumeric

import (
	"fmt"
	"math"
)

func LinearAdj(v ...float64) (float64, float64, error) {
	if len(v)%2 != 0 {
		return 0, 0, fmt.Errorf("v must be x1, y1, x2, y2, ... with an even number of elements")
	}
	x := []float64{}
	y := []float64{}

	for i := 0; i < len(v); i++ {
		if i%2 == 0 {
			x = append(x, v[i])
		} else {
			y = append(y, v[i])
		}
	}

	N := float64(len(x))
	sX := sumX(x)
	sX2 := sumX2(x)
	sY := sumX(y)
	sXY := sumXY(x, y)

	d := N*sX2 - math.Pow(sX, 2)
	a1 := (sX2*sY - sX*sXY) / d
	a2 := (N*sXY - sX*sY) / d

	return a1, a2, nil
}

func sumX(x []float64) float64 {
	s := 0.
	for i := 0; i < len(x); i++ {
		s += x[i]
	}

	return s
}

func sumX2(x []float64) float64 {
	s := 0.
	for i := 0; i < len(x); i++ {
		s += math.Pow(x[i], 2)
	}

	return s
}

func sumXY(x, y []float64) float64 {
	s := 0.
	for i := 0; i < len(x); i++ {
		s += x[i] * y[i]
	}

	return s
}

func ArithmeticMean(x ...float64) float64 {
	s := 0.
	for _, xi := range x {
		s += xi
	}

	return s / float64(len(x))
}

func QuadraticMean(x ...float64) float64 {
	s := 0.
	for _, xi := range x {
		s += math.Pow(xi, 2)
	}

	return math.Sqrt(s / float64(len(x)))
}

func MidRange(x ...float64) float64 {
	return (Max(x...) + Min(x...)) / 2.
}

func Max(x ...float64) float64 {
	xmax := x[0]
	for i := 1; i < len(x); i++ {
		if xmax < x[i] {
			xmax = x[i]
		}
	}

	return xmax
}

func Min(x ...float64) float64 {
	xmin := x[0]
	for i := 0; i < len(x); i++ {
		if xmin > x[i] {
			xmin = x[i]
		}
	}
  
	return xmin
}

O arquivo poly.go possui apenas uma função para encontrar raízes de polinômio de segundo grau: RootsPoly2.

package cnumeric

import "math"

func RootsPoly2(a, b, c float64) (x1, x2 interface{}) {
	a2 := 2. * a
	delta := math.Pow(b, 2) - 4*a*c
	if delta >= 0 {
		// Real roots
		sq := math.Sqrt(delta)
		x1 = (-b + sq) / a2
		x2 = (-b - sq) / a2
		return
	}
	// Complex roots
	x1 = complex(-b/a2, math.Sqrt(-delta)/a2)
	x2 = complex(-b/a2, -math.Sqrt(-delta)/a2)
	return
}

Como ambos os arquivos se encontram no mesmo diretório, ambos possuem a mesma declaração de pacote: package cnumeric.

Package main

Neste momento o pacote main é o arquivo calc.go no diretório raiz do projeto. Seu conteúdo é apenas alguns testes das funções e tipos definidos nos outros pacotes.

package main

import (
	"fmt"
	"math"
	"mycodes/calc/cmath"
	"mycodes/calc/cnumeric"
)

func main() {
	m1 := cmath.Matrix{}
	m1.StartMatrix(2, 2, 3., 2., 5., -1.)
	m2 := cmath.Matrix{}
	m2.StartMatrix(2, 3, 6., 4, -2., 0., 7., 1.)

	fmt.Println("m1:")
	fmt.Println(m1)

	fmt.Println("m2:")
	fmt.Println(m2)

	fmt.Println("m1+m2:")
	z, err := m1.Add(m2)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(z)
	}

	fmt.Println("\nm1*m2:")
	z, _ = m1.Product(m2)
	fmt.Println(z)

	v1 := cmath.Vector{X: 2., Y: 3., Z: 4.}
	v2 := cmath.Vector{X: 5., Y: 5., Z: 2.}

	fmt.Println("v1:", v1, " v2:", v2)

	x1, x2 := cnumeric.RootsPoly2(2, 3, -5)
	fmt.Printf("\n2x² + 3x - 5 = 0:\nx1 = %+1.2f x2 = %+1.2f\n", x1, x2)

	fmt.Println("\nLinear adjust of points {1., 1.2, 1.5, 1.3, 2., 2.3}")
	a1, a2, _ := cnumeric.LinearAdj(1., 1.2, 1.5, 1.3, 2., 2.3)
	fmt.Printf("b = %+1.2f m = %+1.2f\n", a1, a2)

	fmt.Println("\nLinear adjust of points {.23, -.54, -.3, -.54, .04, -.57}")
	a1, a2, _ = cnumeric.LinearAdj(.23, -.54, -.3, -.54, .04, -.57)
	fmt.Printf("b = %+1.2f m = %+1.2f\n", a1, a2)

	fmt.Println("\nLinear adjust of points {-.35, .2, .15, -.5, .23, .54, .35, .7}")
	a1, a2, _ = cnumeric.LinearAdj(-.35, .2, .15, -.5, .23, .54, .35, .7)
	fmt.Printf("b = %+1.2f m = %+1.2f\n", a1, a2)

	fmt.Println("\n", math.Sqrt(2))
}

Sobre este pacote tem dois pontos importantes a salientar:

  1. Todo pacote main deve ter uma função main – a função main é por onde o aplicativo inicia a execução, portanto a sua declaração (func main() {...}) é esperado no pacote main;
  2. para acessar os pacotes internos no projeto é necessário importar com um
    import path_do_projeto/subdiretorio_do_pacote;
  3. as chamadas aos tipos e funções do pacotes do projeto empregam apenas nome_do_pacote.tipo/função, ou seja, para usar o tipo Matrix do pacote cmath basta chamar cmath.Matrix.

Lembre-se que este projeto está alocado na pasta $GOPATH/src/mycodes/calc, desta forma o import deve conter mycode/calc/subdiretorio_do_pacote, já que o $GOPAH/src é a pasta padrão do Go.

Biblioteca Padrão vs Local

Se mudar o nome do pacote cmath em vectors.go e matrix.go para apenas math a chamada da linha 54, cálculo da raiz de 2, não vai encontrar a função Sqrt, já que esta não está definida neste pacote, mas sim na biblioteca padrão.

É possível contornar isto fazendo um alias para função da biblioteca padrão math.Sqrt adicionando um código como na linha 8 abaixo:

package math

import (
	"fmt"
	"math"
)

var Sqrt = math.Sqrt

type Vector struct {
	X float64
	Y float64
	Z float64
}
...

Isto limita as funções da biblioteca padrão às funções pré-definidas no padote local, o que pode ser tedioso e pouco produtivo.

Uma outra forma seria criar um alias para a bibliotaca padrão adicionando um pseudônimo no import no código do calc.go como abaixo:

  package main

import (
	"fmt"
	Math "math"
	math "mycodes/calc/cmath"
	"mycodes/calc/cnumeric"
)

func main() {
	m1 := math.Matrix{}
	m1.StartMatrix(2, 2, 3., 2., 5., -1.)
	m2 := math.Matrix{}
	m2.StartMatrix(2, 3, 6., 4, -2., 0., 7., 1.)
...
	fmt.Println("\n", Math.Sqrt(2))
}

Neste caso Math, com “M” maiúsculo, fica reservado para a biblioteca padrão e math, com “m” minúsculo, para a biblioteca local. Pessoalmente não me agrada nem uma das opções e acho mais prudente, e menos propício a erros, evitar usar nomes de pacotes da biblioteca padrão, ou mesmo de terceiros, que tenha de usar em seu projeto.

Para terminar esta parte execute o código no terminal com o go run:

Bash

alves@arabel:.../calc$ go run calc.go
m1:
[+3.00 +2.00]
[+5.00 -1.00]

m2:
[+6.00 +4.00 -2.00]
[+0.00 +7.00 +1.00]

m1+m2:
it is not possible to add matrices of different sizes

m1*m2:
[+18.00 +26.00 -4.00]
[+30.00 +13.00 -11.00]

v1: (2.00i + 3.00j + 4.00k) v2: (5.00i + 5.00j + 2.00k)

2x² + 3x – 5 = 0:
x1 = +1.00 x2 = -2.50

Linear adjust of points {1., 1.2, 1.5, 1.3, 2., 2.3}
b = -0.05 m = +1.10

Linear adjust of points {.23, -.54, -.3, -.54, .04, -.57}
b = -0.55 m = -0.01

Linear adjust of points {-.35, .2, .15, -.5, .23, .54, .35, .7}
b = +0.19 m = +0.47

1.4142135623730951

Documentando o Código

Que documentação é um componente fundamental para o desenvolvimento e manutenção de um software não há dúvidas. Uma documentação incompleta e desatualizada gera diversos impactos como riscos de erros e ineficiência no seu desenvolvimento, bem como no manuseio.

O Go traz uma proposta diferenciada para a produção da documentação de seu código, mais focada na facilidade de escrita e manutenção desta documentação. Ao invés de empregar arquivos de documentos separados, e muitas vezes editado por uma conjunto diferente de ferramentas, no Go esta é acoplada diretamente ao código na forma de comentários, para que a documentação evolua juntamente com o código.

A geração da documentação fica delegado a ferramenta Godoc, um aplicativo desenvolvido pelo time do Go para extrair as informações das linhas de comentário do código, seguindo métricas simples, para assim gerar a documentação.

O Godoc analisa o código fonte e produz uma a documentação empregando algumas poucas convenções que serão descritas a seguir. No entanto, caso ainda não tenha instalado o Godoc, execute a linha abaixo para fazê-lo:

Bash

alves@arabel:~$ go install golang.org/x/tools/cmd/godoc@latest

Comentários em Go

Antes de iniciar e documentação vamos registrar como se fazer comentários em Go, o qual pode ser feito de duas formas:

  • comentário de uma linha: ao adicionar um “//” à linha, tudo que aparecer em seguida as duas barras será considerado comentário;
    // Isto e uma linha de comentário e será ignorada pelo compilador
  • comentário de multiplas linhas: comentários de várias linhas são feitos envolvendo as linhas por um “/*” para iniciar e terminando com um “*/” para terminar o comentário.
    /*
    Isto são diversas linhas
    de comentários. Diferente
    de uma linha de comentário
    que termina ao final da linha
    este termina
    ao fechar com um
    */

Convenções na Documentação

As convenções para se criar uma documentação no Go são simples e são focados na ideia de que “os comentários do Godoc são apenas bons comentários, do tipo que você gostaria de ler mesmo se o Godoc não existisse.”

Para discutir as regras para se gerar uma boa documentação com o Godoc será desenvolvidos comentários sobre o projeto calc apresentado até o momento. Para acompanhar as alterações e efeitos na documentação, acompanhe sua evolução no servidor local de documentação gerado pelo comando godoc com a linha a seguir:

Bash

alves@arabel:~$ godoc -http=localhost:8080
using module mode; GOMOD=/home/alves/go/src/golang.org/x/tools/go.mod

Isto irá executar um servidor com a documentação na porta 8080. Em seguida acesse seu conteúdo em um browser no endereço http://localhost:8080. Isto deve lhe dar acesso a uma página semelhante a imagem a seguir:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Neste caso a Standard library, a biblioteca padrão, não é de interesse e por isto já está fechada, clique no triângulo escuro à frente do texto Standard library para fechar, o conteúdo de interesse está em Thrid party, ou seja, código de terceiros, onde se encontra o mycodes desenvolvido na seção anterior.

Em seguida pesquise por mycodes (CNTRL+F) nesta página e encontre a linha mostrada na figura abaixo:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Documentando Tipos, Variáveis, Constantes, Funções e Pacotes

1. Para documentar uma declaração no Go basta adicionar um comentário normal diretamente antes da declaração, sem linhas em branco.

Aqui declaração se refere a qualquer tipo de declaração como um tipo, constante, declaração de variáveis, função, interface ou mesmo um pacote. Altere o código do calc.go com a adição dos comentários a seguir:

/*
Estas linhas de comentário não aparecem na documentação
pois é seguida de uma linha e branco
*/

/*
Este comentário gera a documentação do pacote calc.
A primeira linha aparecerá como uma documentação curta
e as demais na página da documentação.

Salve e observe o que acontece.
*/
package main

import (
	"fmt"
	"math"
	"mycodes/calc/cmath"
	"mycodes/calc/cnumeric"
)

func main() {
	m1 := cmath.Matrix{}
	m1.StartMatrix(2, 2, 3., 2., 5., -1.)
	m2 := cmath.Matrix{}
	m2.StartMatrix(2, 3, 6., 4, -2., 0., 7., 1.)

	fmt.Println("m1:")
...

Ao adicionar as linhas de comentários acima dê um refresh na página (se necessário pare e reinicie o servidor de documentação no terminal, CNTRL+C e execute novamente).

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Observe que o comentário “Este comentário gera a documentação do pacote calc.” foi adicionado como uma descrição curta do programa. Clicando em calc vai abrir a documentação como segue:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Como pode observar todo o comentário acima da declaração package main foi adicionado como documentação do aplicativo calc, já o primeiro comentário, das linhas 1 à 4, foram ignorados pois não precedem nenhuma declaração.

Em geral é de praxe empregar um comentário como o primeiro adicionado ao calc.go para declaração da licença ou copyright do código. No momento altere os comentários para gerar uma documentação mais adequada.

/*
Copyright 2022 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/

/*
Calc é um projeto de uma calculadora científica.

No momento Calc possui duas bibliotecas com tipos e funções para
o cálculo com Vetores e Matrizes.
*/
package main

import (
	"fmt"
	"math"
	"mycodes/calc/cmath"
	"mycodes/calc/cnumeric"
)

func main() {
	m1 := cmath.Matrix{}
...

O arquivo calc.go não tem mais documentação a adicionar. A função main é uma função internado projeto (iniciada com letras minúsculas) e por isto não é exportada. Funções, constantes, variáveis e tipos internas podem ser documentadas em linhas de comentários no código, mas não são exportados na documentação.

Os arquivos matrix.go e vector.go documentados são apresentados na sequência:

/*
Copyright 2022 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/

package cmath

import (
	"fmt"
	"math"
)

// Vector retorna um vetor tridimensional com suporte
// às operações convencionais de vetor.
type Vector struct {
	X float64
	Y float64
	Z float64
}

// Norm retorna a norma (módulo) do vetos corrente.
func (v Vector) Norm() float64 {
	return math.Sqrt(math.Pow(v.X, 2) + math.Pow(v.Y, 2) + math.Pow(v.Z, 2))
}

// Add retorna o vetor resultante da soma do vetor
// corrente, v, pelo vetor w
func (v Vector) Add(w Vector) Vector {
	return Vector{v.X + w.X, v.Y + w.Y, v.Z + w.Z}
}

// Sub retorna o vetor resultante da subtração do vetor
// corrente, v, pelo vetor w
func (v Vector) Sub(w Vector) Vector {
	return Vector{v.X - w.X, v.Y - w.Y, v.Z - w.Z}
}

// RealProd retorna o vetor resultante do produto do
// vetor corrente, v, pela constante real r.
func (v Vector) RealProd(r float64) Vector {
	return Vector{v.X * r, v.Y * r, v.Z * r}
}

// DotProd retorna o produto escalar do vetor
// corrente, v, pelo vetor w.
func (v Vector) DotProd(w Vector) float64 {
	return v.X*w.X + v.Y*w.Y + v.Z*w.Z
}

// CrossProd retorna o produto vetorial entre o
// vetor corrente, v, pelo vetor w.
func (v Vector) CrossProd(w Vector) Vector {
	return Vector{
		v.Y*w.Z - v.Z*w.Y,
		v.Z*w.X - v.X*w.Z,
		v.X*w.Y - v.Y*w.X,
	}
}

// String cria uma saída formatada para a matriz e faz
// com que Matrix seja parte dor tipos que satisfazem a
// interface fmt.Stringer.
func (v Vector) String() string {
	return fmt.Sprintf("(%3.2fi + %3.2fj + %3.2fk)", v.X, v.Y, v.Z)
}

No vectors.go não foi adicionado comentário à frente da declaração package, deixando para adicionar no arquivo matrix.go.

/*
Copyright 2022 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/

// cmath possui tipos, atributos e métodos para o cálculo de
// matrizes e vetores. Este pacote contém dois arquivos com
// as definições de matriz e vetores.
package cmath

import (
	"fmt"
)

// Matrix declara um tipo matriz que permite fazer operações
// de soma, subtração e produtos conforme as regras de
// manipulação de matriz.
//
// Os elementos da matriz podem ser inicializados pelos
// métodos StratMatrix e ZerosMatrix.
type Matrix struct {
	elements [][]float64
	rows     int
	cols     int
}

// setRowsCols configura os atributos rows e cols da matriz
// atraves da matriz de elementos.
func (m *Matrix) setRowsCols() {
	if m.rows != len(m.elements) {
		m.rows = len(m.elements)
	}
	if m.cols != len(m.elements[0]) {
		m.cols = len(m.elements[0])
	}
}

// GetElement retorna o elementos da matriz da linha row e
// coluna col. Um erro é gerado se o elemento solicitado
// exceder as dimensões da matriz.
func (m Matrix) GetElement(row, col int) (float64, error) {
	if row >= 0 && row < m.rows && col >= 0 && col < m.cols {
		return m.elements[row][col], nil
	}
	return 0., fmt.Errorf("there is not element [%d][%d] in this matrix", row, col)
}

// SetElement altera o elementos da matriz da linha row e
// coluna col com o valor v. Um erro é gerado se o elemento
// solicitado exceder as dimensões da matriz.
func (m *Matrix) SetElement(row, col int, v float64) error {
	if row >= 0 && row < m.rows && col >= 0 && col < m.cols {
		m.elements[row][col] = v
		return nil
	}
	return fmt.Errorf("there is not element [%d][%d] in this matrix", row, col)
}

// StartMatrix inicia uma matriz com r linhas e c colunas
// com os elementos passados pela lista e. Um erro é gerado
// se o número de elementos não corresponder ao produto
// r*c.
func (m *Matrix) StartMatrix(r int, c int, e ...float64) error {
	if r*c != len(e) {
		return fmt.Errorf("rows (%d) x columns (%d) is different from the number of matrix elements (%d)", r, c, len(e))
	}

	for i := 0; i < r; i++ {
		row := []float64{}
		for j := 0; j < c; j++ {
			row = append(row, e[i*c+j])
		}
		m.elements = append(m.elements, row)
	}
	m.setRowsCols()

	return nil
}

// ZerosMatrix inicia uma matriz com elementos zeros com
// r linhas e c colunas.
func (m *Matrix) ZerosMatrix(r, c int) {
	for i := 0; i < r; i++ {
		row := make([]float64, c)
		m.elements = append(m.elements, row)
	}

	m.setRowsCols()
}

// String cria uma saída formatada para a matriz e faz
// com que Matrix seja parte dor tipos que satisfazem a
// interface fmt.Stringer.
func (m Matrix) String() string {
	str := "["
	for r := 0; r < m.rows; r++ {
		for c := 0; c < m.cols; c++ {
			str += fmt.Sprintf("%+3.2f ", m.elements[r][c])
		}
		str = str[:len(str)-1] + "]\n["
	}
	str = str[:len(str)-1]

	return str
}

// Product retorna o produto entre a matriz corrente, m,
// e a matriz w. Um erro é gerado se o número de colunas
// de m for diferente do número de linhas de w.
func (m Matrix) Product(w Matrix) (Matrix, error) {
	if m.cols != w.rows {
		return Matrix{}, fmt.Errorf("in A*B the A.columns must be equal to B.rows")
	}

	z := Matrix{}
	z.ZerosMatrix(m.rows, w.cols)

	for i := 0; i < z.rows; i++ {
		for j := 0; j < z.cols; j++ {
			for k := 0; k < z.rows; k++ {
				z.elements[i][j] += m.elements[i][k] * w.elements[k][j]
			}
		}
	}

	return z, nil
}

// Compare compara a matriz corrente, m, com a matriz
// e retorna true se forem iguais
func (m Matrix) Compare(w Matrix) bool {
	if m.cols != w.cols || m.rows != w.rows {
		return false
	}

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			if m.elements[i][j] != w.elements[i][j] {
				return false
			}
		}
	}

	return true
}

// RealProduct retorna uma matriz produto da Matrizes
// corrente, m, por uma constante real c.
func (m Matrix) RealProduct(c float64) Matrix {
	w := Matrix{}
	w.ZerosMatrix(m.rows, m.cols)

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			w.elements[i][j] = m.elements[i][j] * c
		}
	}

	return w
}

// Add retorna a soma da matriz corrente, m, pela matriz
// w. Um erro é gerado se as matrizes possuírem dimensões
// diferentes.
func (m Matrix) Add(w Matrix) (Matrix, error) {
	if m.rows != w.rows || m.cols != w.cols {
		return Matrix{}, fmt.Errorf("it is not possible to add matrices of different sizes")
	}
	z := Matrix{}
	z.ZerosMatrix(m.rows, m.cols)

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			z.elements[i][j] = m.elements[i][j] + w.elements[i][j]
		}
	}

	return z, nil
}

// Sub retorn a subtração da matriz corrente, m, pela
// matriz w. Um erro é gerado se as matrizes possuírem
// dimensões diferentes.
func (m Matrix) Sub(w Matrix) (Matrix, error) {
	if m.rows != w.rows || m.cols != w.cols {
		return Matrix{}, fmt.Errorf("it is not possible to subtract matrices of different sizes")
	}
	z := Matrix{}
	z.ZerosMatrix(m.rows, m.cols)

	for i := 0; i < m.rows; i++ {
		for j := 0; j < m.cols; j++ {
			z.elements[i][j] = m.elements[i][j] - w.elements[i][j]
		}
	}

	return z, nil
}

// Det2 retorna o determinante de uma matriz 2x2.
// Retorna um erro se a matriz não for 2x2.
func (m Matrix) Det2() (float64, error) {
	if m.rows != 2 || m.cols != 2 {
		return 0., fmt.Errorf("is not a 2x2 matrix")
	}

	e := m.elements
	d := e[0][0]*e[1][1] - e[0][1]*e[1][0]

	return d, nil
}

// Det3 retorna o determinante de uma matriz 3x3.
// Retorna um erro se a matriz não for 3x3.
func (m Matrix) Det3() (float64, error) {
	if m.rows != 3 || m.cols != 3 {
		return 0., fmt.Errorf("is not a 3x3 matrix")
	}
	e := m.elements
	d := e[0][0]*(e[1][1]*e[2][2]-e[1][2]*e[2][1]) -
		e[0][1]*(e[1][0]*e[2][2]-e[1][2]*e[2][0]) +
		e[0][2]*(e[1][0]*e[2][1]-e[1][1]*e[2][0])

	return d, nil
}

Observe que os comentários estão sempre juntos à declaração da função e tipo empregados no arquivo, aplicando a regra 1, acima.

Se acessado novamente o http://localhost:8080/pkg/mycodes/calc/ observe que agora apareceu uma descrição curta ao cmath, como ilustra a figura a seguir:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Esta descrição curta segue a segunda regra de documentação a pacotes:

2. A primeira frase precedendo uma declaração package é empregada como uma descrição curta do pacote.

Se observado no pacote cmath o tipo Matrix como o Vector estão devidamente documentados:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Por fim adicione a documentação ao pacote cnumeric adicionando comentários aos arquivos linear.go e poly.go.

/*
Copyright 2022 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/

// cnumeric contém funções para a calculo
// de raízes, funções estatísticas e ajustes de curvas.
package cnumeric

import (
	"fmt"
	"math"
)

// LinearAdj retorna os coeficientes a1 e a2 do ajuste
// linear a reta y = a1 + a2*x para os conjunto de dados
// x1, y1, x2, y2, x3, y3, ... passados na entrada. Um
// erro é gerado se o número do conjunto de dados passados
// não for par.
func LinearAdj(v ...float64) (float64, float64, error) {
	if len(v)%2 != 0 {
		return 0, 0, fmt.Errorf("v must be x1, y1, x2, y2, ... with an even number of elements")
	}
	x := []float64{}
	y := []float64{}

	for i := 0; i < len(v); i++ {
		if i%2 == 0 {
			x = append(x, v[i])
		} else {
			y = append(y, v[i])
		}
	}

	N := float64(len(x))
	sX := sumX(x)
	sX2 := sumX2(x)
	sY := sumX(y)
	sXY := sumXY(x, y)

	d := N*sX2 - math.Pow(sX, 2)
	a1 := (sX2*sY - sX*sXY) / d
	a2 := (N*sXY - sX*sY) / d

	return a1, a2, nil
}

// sumX retorna a soma de todos os elementos passados
func sumX(x []float64) float64 {
	s := 0.
	for i := 0; i < len(x); i++ {
		s += x[i]
	}

	return s
}

// sumX2 retorna a soma xi² de todos os elmentos passados
func sumX2(x []float64) float64 {
	s := 0.
	for i := 0; i < len(x); i++ {
		s += math.Pow(x[i], 2)
	}

	return s
}

// sumXY retorna a soma xi*yi de todos elementos passados
func sumXY(x, y []float64) float64 {
	s := 0.
	for i := 0; i < len(x); i++ {
		s += x[i] * y[i]
	}

	return s
}

// ArithmeticMean retorna a média aritmética dos x...
func ArithmeticMean(x ...float64) float64 {
	s := 0.
	for _, xi := range x {
		s += xi
	}

	return s / float64(len(x))
}

// QuadraticMean retorna a média quadrática dos x...
func QuadraticMean(x ...float64) float64 {
	s := 0.
	for _, xi := range x {
		s += math.Pow(xi, 2)
	}

	return math.Sqrt(s / float64(len(x)))
}

// MidRange retorna o valor médio dos valores passados
func MidRange(x ...float64) float64 {
	return (Max(x...) + Min(x...)) / 2.
}

// Max retorna o máximo dos valores passados
func Max(x ...float64) float64 {
	xmax := x[0]
	for i := 1; i < len(x); i++ {
		if xmax < x[i] {
			xmax = x[i]
		}
	}

	return xmax
}

// Min retorna o mínimo dos valores passados
func Min(x ...float64) float64 {
	xmin := x[0]
	for i := 0; i < len(x); i++ {
		if xmin > x[i] {
			xmin = x[i]
		}
	}

	return xmin
}

e

/*
Copyright 2022 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/

package cnumeric

import (
	"math"
)

// RootsPoly2 retorna as raízes reais ou complexas do
// polinômio de segunda ordem ax²+bx+c=0
func RootsPoly2(a, b, c float64) (x1, x2 interface{}) {
	a2 := 2. * a
	delta := math.Pow(b, 2) - 4*a*c
	if delta >= 0 {
		// Real roots
		sq := math.Sqrt(delta)
		x1 = (-b + sq) / a2
		x2 = (-b - sq) / a2
		return
	}
	// Complex roots
	x1 = complex(-b/a2, math.Sqrt(-delta)/a2)
	x2 = complex(-b/a2, -math.Sqrt(-delta)/a2)
	return
}

Apenas para ilustra adicione o arquivo definitions.go ao diretório cnumeric:

package cnumeric

// Some physical constants
const (
	E  = 1.602176634e-19  // Elementary charge
	G  = 6.67430e-11      // Newtonian constant of gravitation
	H  = 6.62607015e-34   // Planck constant
	C  = 299792458        // Speed of light in vacuum
	E0 = 8.8541878128e-12 // Vacuum electric permittivity
	M0 = 1.25663706212e-6 // Vacuum magnetic permeability
	Me = 9.1093837015e-31 // Electron mass
)

// Channel é uma variáveis testes
var (
	Channel0 chan int // Canal 0
	Channel1 chan int // Canal 1
)

Este adiciona algumas constantes físicas ao projeto. Por fim verifique a documentação completa do projeto calc, em particular a documentação do pacote cnumeric.

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Constantes e variáveis são apresentadas em uma seção separada no início da documentação. As variáveis Channel0 e Channel1 são apenas a mais clara falta de imaginação do que adicionar.

Arquivo doc.go

Para uma documentações mais complexas é aconselhável adicionar um arquivo doc.go no pacote a ser documentado. Neste exemplo será adicionado um doc.go apenas no diretório principal, mas o mesmo pode ser adicionado nos demais pacotes a fim de criar uma documentação mais robusta dos demais pacotes.

Para isto remova os comentário precedente a a declaração “package main“, no arquivo calc.go, e em seguida crie o arquivo doc.go na pasta calc:

/*
Copyright 2022 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/

/*
O Projeto calc é uma proposta de implementação de uma calculadora
científica especializada. Este ainda está em desenvolvimento,
com várias pendência e possibilidades ainda em aberto.

# Componentes

Neste momento calc possui dois módulos:

  - cmath - com tipos, atributos e métodos para operar com
    matrizes e vetores;
  - cnumeric - com funções estatíticas e calculo de raízes
    de polinômios (no momento apenas implementado plinômios de ordem 2).

O projeto ainda está em fase embriatória não possuíndo
interface de usuário, mas apenas no desenvolvimento da ferramentas.

# Adições Futuras

O projeto prevê a adição das seguintes funcionalidades:
 1. cmath: adicionar resolução d auto-valores e auto-vetores;
 2. cmath: adicionar diagonização de matriz, matriz inversa,
    adjunta e transposta.
 3. cmath: adicionar calculo de determinante para matriz NxN.
 4. calc: adicionar interface gráfica empregar o go-gtk,
    (https://github.com/mattn/go-gtk)
 5. calc: implementar um interpretador de expressões numéricas
    para converter "(a+b)xc" em "CrossProd(a.add(b),c)"
*/
package main

Aqui podemos ver em ação mais três regrinhas para a formatação da documentação. Sendo a primeira para colocação de títulos.

3. Um título deve iniciar pelo caractere “#”.

Na versão anterior os Títulos deveriam ser apenas frases curtas, terminadas sem pontuação e com linhas vazias a sua volta. Este formado ainda é aceito como título pelo Godoc, mas aconselha-se muda para o novo padrão, no entanto ainda é possível encontrar alguns projetos empregando o padrão anterior.

Imagino que se deve adicionar suporte para subtítulo com duas ou mais tralhas para isto, mas até o momento isto não aconteceu.

4. Listas são criadas com itens iniciados por um ou mais espaços (ou tabelamento) em brancos e seguidos dos caracteres “-” ou “*”;

5. Listas numeradas são criadas com itens iniciados por um ou mais espaços (ou tabelamento) em brancos de um número e um ponto;

Até o momento listas encadeadas não são suportadas, apenas simples listas numeradas ou não.

Verifique a documentação gerada pelos comentários do doc.go:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

A última regra disponível até o momento é sobre a adição trechos de código na documentação:

6. Techos de código podem ser adicionados como linhas iniciadas com espaços (ou tabelamento) seguido do código devidamente indentado.

O trecho de comentário a seguir:

/*
...

# Exemplo de Código

Para adicionar um trecho de código basta colar o código com
algum espaço ou tabelamento no primeiro caractere:

	x := []float64{}
	y := []float64{}

	for i := 0; i < len(v); i++ {
	  if i%2 == 0 {
	    x = append(x, v[i])
	  } else {
	    y = append(y, v[i])
	  }
	}
...
*/
package main

Este comentário gerar a documentação:

[bdrimg w=’2px 2px 2px 2px’ bc=’#000000′]

[/bdrimg]

Considerações Finais

Ainda existem alguns pontos sobre documentação os quais serão apresentados no próximo artigo sobre adição de testes aos pacotes. No entanto o apresentado até aqui já dá uma boa base para construir uma documentação robusta e simples de manter.

Deixe um comentário

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.