Go: log/return

Hoje eu quero falar sobre algo que tive muita duvida no começo e hoje ainda me incomoda quando vejo. Quando estamos construindo um aplicativo é necessário gerenciar erros e reporta-los de alguma forma.

Imagine que você tem um handler e ele receba parâmetros de paginação:

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

const defaultPageSize = 10

func list(w http.ResponseWriter, r *http.Request) error {
	page := 0
	size := defaultPageSize

	if p := strings.TrimSpace(r.FormValue("page")); p != "" {
		pint, err := strconv.Atoi(p)
		if err != nil {
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return err
		}
		page = pint
	}
	if s := strings.TrimSpace(r.FormValue("size")); s != "" {
		sint, err := strconv.Atoi(s)
		if err != nil {
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return err
		}
		size = sint
	}

	fmt.Fprintf(w, "offset: %d\n", page*size)

	return nil
}

func errWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := handler(w, r)
		if err != nil {
			log.Println("ERROR:", err)
		}
	})
}

func main() {

	port := os.Getenv("PORT")
	if port == "" {
		port = ":3000"
	}

	http.Handle("/", errWrapper(list))

	log.Printf("app at http://localhost%s\n", port)
	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatalf("abrupt stop of app: %v", err)
	}

}

Provavelmente você pode começar com algo assim, apenas retornando o erro que pode ocorrer no strconv. Depois de alguns testes você repara que não consegue saber de qual strconv surge o problema, temos duas conversões e elas retornam a mesma mensagem. Algumas pessoas vão sugerir inserir uma mensagem de log onde ocorre o problema.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

const defaultPageSize = 10

func list(w http.ResponseWriter, r *http.Request) error {
	page := 0
	size := defaultPageSize

	if p := strings.TrimSpace(r.FormValue("page")); p != "" {
		pint, err := strconv.Atoi(p)
		if err != nil {
			log.Printf("list; cannot convert page to int: %v\n", err)
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return err
		}
		page = pint
	}
	if s := strings.TrimSpace(r.FormValue("size")); s != "" {
		sint, err := strconv.Atoi(s)
		if err != nil {
			log.Printf("list; cannot convert size to int: %v\n", err)
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return err
		}
		size = sint
	}

	fmt.Fprintf(w, "offset: %d\n", page*size)

	return nil
}

func errWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := handler(w, r)
		if err != nil {
			log.Println("ERROR:", err)
		}
	})
}

func main() {

	port := os.Getenv("PORT")
	if port == "" {
		port = ":3000"
	}

	http.Handle("/", errWrapper(list))

	log.Printf("app at http://localhost%s\n", port)
	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatalf("abrupt stop of app: %v", err)
	}

}

Corrige o problema, mas agora você tem duas mensagens passando informações semelhantes. A saída da aplicação ficaria assim:

list; cannot convert size to int: strconv.Atoi: parsing "10,": invalid syntax
ERROR: strconv.Atoi: parsing "10,": invalid syntax

Parece um custo pequeno no começo o ganho de uma linha extra, mas as coisas vão se acumulando com o tempo. Passa um mês e você + equipe já colocaram log em cada erro possível. Linhas que não contribuem diretamente com a lógica necessária para se atingir o objetivo desejado são consideradas ruído. Aqui nós temos ruído para o programador que vai realizar a manutenção do código e para a parte de sustentação que as vezes precisa identificar rapidamente um problema e vai encontrar linhas “duplicadas”.

Uma das formas de mitigar o problema é seguir uma regra de quando devemos enriquecer a mensagem de erro ou não. A mensagem de log que utilizamos acima enriquece sem tocar no erro, mas podemos fazer isso diretamente, eliminando assim a necessidade do log.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

const defaultPageSize = 10

func list(w http.ResponseWriter, r *http.Request) error {
	page := 0
	size := defaultPageSize

	if p := strings.TrimSpace(r.FormValue("page")); p != "" {
		pint, err := strconv.Atoi(p)
		if err != nil {
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return fmt.Errorf("list; cannot convert page to int: %w", err)
		}
		page = pint
	}
	if s := strings.TrimSpace(r.FormValue("size")); s != "" {
		sint, err := strconv.Atoi(s)
		if err != nil {
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return fmt.Errorf("list; cannot convert size to int: %w", err)
		}
		size = sint
	}

	fmt.Fprintf(w, "offset: %d\n", page*size)

	return nil
}

func errWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := handler(w, r)
		if err != nil {
			log.Println("ERROR:", err)
		}
	})
}

func main() {

	port := os.Getenv("PORT")
	if port == "" {
		port = ":3000"
	}

	http.Handle("/", errWrapper(list))

	log.Printf("app at http://localhost%s\n", port)
	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatalf("abrupt stop of app: %v", err)
	}

}

E a nossa saída:

ERROR: list; cannot convert size to int: strconv.Atoi: parsing "10,": invalid syntax

Menos ruído, mais informação e temos uma noção de onde ocorreu o problema no código. O ideal agora era saber exatamente onde ocorreu o problema através de uma stack trace.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/pkg/errors"
)

const defaultPageSize = 10

func list(w http.ResponseWriter, r *http.Request) error {
	page := 0
	size := defaultPageSize

	if p := strings.TrimSpace(r.FormValue("page")); p != "" {
		pint, err := strconv.Atoi(p)
		if err != nil {
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return errors.Wrap(err, "cannot convert page to int")
		}
		page = pint
	}
	if s := strings.TrimSpace(r.FormValue("size")); s != "" {
		sint, err := strconv.Atoi(s)
		if err != nil {
			http.Error(w, "Oops something went wrong", http.StatusBadRequest)
			return errors.Wrap(err, "cannot convert page to int")
		}
		size = sint
	}

	fmt.Fprintf(w, "offset: %d\n", page*size)

	return nil
}

func errWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := handler(w, r)
		if err != nil {
			log.Printf("ERROR: %+v\n", err)
		}
	})
}

func main() {

	port := os.Getenv("PORT")
	if port == "" {
		port = ":3000"
	}

	http.Handle("/", errWrapper(list))

	log.Printf("app at http://localhost%s\n", port)
	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatalf("abrupt stop of app: %v", err)
	}

}

Saída com stack trace:

ERROR: strconv.Atoi: parsing "10,": invalid syntax
cannot convert page to int
main.list
        C:/Users/CLIENTE/Documents/code/go/logreturn/main.go:32
main.errWrapper.func1
        C:/Users/CLIENTE/Documents/code/go/logreturn/main.go:44
net/http.HandlerFunc.ServeHTTP
        c:/go/src/net/http/server.go:2012
net/http.(*ServeMux).ServeHTTP
        c:/go/src/net/http/server.go:2387
net/http.serverHandler.ServeHTTP
        c:/go/src/net/http/server.go:2807
net/http.(*conn).serve
        c:/go/src/net/http/server.go:1895
runtime.goexit
        c:/go/src/runtime/asm_amd64.s:1373

Assim nem precisamos mais colocar o nome do handler na mensagem de erro (list), já que o mesmo vai ser exibido e identificado o arquivo:linha onde ocorreu o primeiro erro na cadeia.

Temos toda informação necessária para avaliar/depurar nossos problemas sem adicionar muito ruído ao projeto inicial. Existe uma boa prática no uso do pacote errors que pode ser encontrada nessa postagem do Dave Cheney. Recomendo d+ seguir esse blog.

Dia produtivo de verdade é aquele que você remove 300 linhas no balanço liquido e mantendo a mesma funcionalidade. Recentemente tive um dia desses só corrigindo error/log handling como escrevi acima. Abraços

Deno. Você vai sentir falta do node_modules/

Deno é um novo runtime js/ts e eu tenho visto tanta gente feliz que não precisa utilizar NPM e aquela pasta gigantesca chamada node_modules/ na raiz do projeto. Como isso foi possível? Primeiro ele abandona a ideia de repositório de pacotes central e permite importação de código direto de uma URL (bem similar ao Go).

# Exemplo em Deno. Importando o assertEquals de uma URL
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

assertEquals("hello", "hello");
assertEquals("world", "world");

console.log("Asserted! ✓");
// Exemplo em Go. Importando toda a API publica do pacote uuid da URL do github
package main

import (
	"fmt"	
	"github.com/google/uuid"
)

func main() {
	fmt.Println(uuid.New())
}

Agora já existe algumas questões:

  • Se a API na URL mudar, meu código pode quebrar?
  • Onde foi parar o arquivo/pacote durante a compilação?
  • Como faço o vendoring para deploy em produção?

Se a API na URL mudar, meu código pode quebrar?

Se você já realizou algum deploy em produção de um projeto real de forma “correta” sabe que diversas linguagens possuem algum tipo de arquivo que contem as dependências e suas versões.

  • PHP/composer: temos o composer.json e composer.lock
  • NodeJS/npm: package.json e package-lock.json
  • Go: go.mod e go.sum

E no Deno? Assim como no Go a ferramenta que gerencia é o próprio binário principal. Certo, e qual o arquivo lista e o lock ? Não tem. Você vai precisar definir o nome/local. Beleza, como fazemos isso? Pela documentação oficial é assim:

deno cache --lock=lock.json --lock-write src/deps.ts

lock.json é o nosso arquivo lock e o src/deps.ts é o nosso arquivo que exporta todas as nossas dependências (não irei entrar no mérito de recomendar um deps gigante). A documentação não deixa claro se ocorre a busca automática da versão da API publica correta. O exemplo do arquivo src/deps.ts inclui a versão na URL, lembrando que muitos repositórios usam a branch master como desenvolvimento e recomendam apenas o uso em uma tag especifica.

# src/deps.ts
export { xyz } from "https://unpkg.com/xyz-lib@v0.9.0/lib.ts";

Então resolvemos o problema da mudança da API publica na URL especificando a versão e criando o arquivo lock.json.

Onde foi parar o arquivo/pacote durante a compilação?

Vamos fazer uma citação dos possíveis locais:

  • On Linux/Redox: $XDG_CACHE_HOME/deno or $HOME/.cache/deno
  • On Windows: %LOCALAPPDATA%/deno (%LOCALAPPDATA% = FOLDERID_LocalAppData)
  • On macOS: $HOME/Library/Caches/deno
  • If something fails, it falls back to $HOME/.deno

Você também pode alterar a variável ambiente $DENO_DIR para mudar o comportamento padrão acima. Então o nosso node_modules/ foi parar fora da pasta do projeto (semelhante ao comportamento do Go).

Como faço o vendoring para deploy em produção?

Aqui você deve precisar fazer alguns passos. Vamos começar baixando as dependências:

# Download the dependencies.
DENO_DIR=./deno_dir deno cache src/deps.ts

Execução + checagem de integridade das dependências:

DENO_DIR=./deno_dir deno run --lock=lock.json --cached-only main.ts

Conclusão

Parece que o deploy em produção não vai ser uma área que o Deno vai simplificar, mas vamos ter esperança. Na linguagem Go por exemplo começou com serias restrições no local do projeto e o vendoring não existia (apenas com ferramentas não oficiais). Hoje o Go tem suporte a todos esses pontos e o Deno apesar de ser um pouco chato nesse ponto, já começou anos luz a frente do Go.

A evolução do ecossistema do js é apontado como negativa pela velocidade que tecnologias surgem e morrem, mas isso também acelera a “morte” do que não funciona tão bem. Se você desenvolve apenas usando React e ainda não deu uma olhada no Svelte você vai ficar bem impressionado…

Testes de programação

Esse ano que passou (2019) apliquei para muitas vagas como desenvolvedor e reparei que houve uma mudança na abordagem. Antes você envia seu CV, esperava uma entrevista e talvez algum tipo de desafios para ter a “certeza” que você sabe programar. Agora, eu envio o CV e já recebo um link de um “desafio” de programação para a vaga pretendida.

O teste geralmente é uma serie de exercícios que você deve completar em um tempo máximo. Os testes são feitos no site de terceiros com uma IDE com descrição do problema e alguns casos de teste para o programador se guiar na implementação. Os desafios na maioria das vezes é implementar algum algoritmo e a partir dessa implementação você é avaliado em alguns pontos. O qualified.io por exemplo exibe esses pontos na sua página:

Em um desses desafios me pediram para implementar um algoritmo que compacta intervalos (1,2,3,4,10 = 1-4, 10). Foi muito divertido fazer esse desafio, principalmente podendo escolher Go como linguagem de implementação e já tendo feito algo semelhante na postagem sobre baixar anime usando o R + irc. Abaixo a minha implementação:

package challenge // import "qualified.io/challenge"

import "fmt"

// seqSeeker runs list and return the longest sequence found
func seqSeeker(list []int) []int {
  seq := []int{}
  for i := 1; i < len(list); i++ {
    if list[i] == list[i-1] + 1 {
      seq = append(seq, list[i])
    } else {
      break
    }
  }
  return seq
}

func Solution(list []int) string {
  r := ""
 
  for i := 0; i < len(list); i++ {
    if i > 0 {
      r += ","
    }
    if seq := seqSeeker(list[i:]); len(seq) >= 2 {
      r += fmt.Sprintf("%d-%d", list[i], seq[len(seq)-1])
      // increment the len(seq) cuz we need to skip elements who are part of sequence
      i += len(seq)
    } else {
      r += fmt.Sprintf("%d", list[i])
    }
  }
  
  return r
}

Esse desafio e mais outros dois para serem concluídos em cinco horas me renderam a pontuação de 85% (imagino carinha feliz aqui) e me levou para a próxima etapa que era marcar a data de uma entrevista. Fiquei bem impressionado como eles confiam na plataforma para selecionar o candidato para uma entrevista sem revisão humana dos códigos. Fica aqui o ponto de reflexão. A minha habilidade como programador web é medida de forma correta usando esses algoritmos ? Já estou empregado a alguns meses e o mais próximo desses desafios que cheguei foi escrever um less(a, b) para implementação de um sort em uma struct.

Quando vamos ser invadidos?

Você abre o ssh e loga no servidor de produção ubuntu e aparece “200 updates are security updates”. Resposta por função na empresa:

  • Desenvolvedor: “Eu não so pago pra atualizar sistema operacional”
  • Infraestrutura: “Ainda não recebi nenhum chamado”

As duas últimas eu ouvi faz alguns anos, mas aconteceu algo recentemente que foi o clímax. Recebo a tarefa de corrigir uma falha de XSS e acabo descobrindo mais dois pontos vulneráveis. Quando relato que existem mais pontos vulneráveis escuto que devo corrigir só o que foi reportado para pararem de serem incomodados por quem reportou.

Quando acontecer a invasão vamos repetir o de sempre: “mas todo mundo foi avisado na reunião”.

100 mil ONGS na Amazônia

Ontem vi algo bizarro, estava assistindo o Poucas com a Luisa Mell e resolvi ligar o chat para ver os comentários que rolaram durante a live. Tinha um perfil que ficava ativamente mandando “100 mil ongs na Amazônia”, depois de um tempo começou a ficar irritante ver a repetição (block e report) mas fiquei intrigado com aquilo. Era a primeira vez que via um bolsominion ou bot sendo tão ativo em tão pouco tempo. E a missão dele funcionava, uma galera já começava a trocar farpas falando que quem acreditava naquilo tinha falta de inteligência.

É, realmente é falta de inteligência. Ano passado me pediram uma lista de contatos do máximo de ONGs possíveis do Brasil. O desafio foi aceito e corri para fazer o crawler dos contatos encontrados no site http://ongsbrasil.com.br. O resultado foi ~23 mil ONGs. A não ser que o Temer tenha feito o maior incentivo da história das ONGs é impossível existir 100 mil delas só na Amazônia.

Mas o que me assusta é um FDP ficar passando fake news tentando FODER o trabalho de alguém que não pensa só nele mesmo. Existir um bot/bolsominion fazendo isso é totalmente falta de caráter. Você ser do contra beleza, mas atrapalhar com mentira é muito medíocre.

Sua mãe te ensinou a não acreditar em tudo que dizem na internet. Se ela ficou senil e compartilha fake news, converse.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/gocarina/gocsv"

	"github.com/andrewstuart/goq"
	"golang.org/x/net/html/charset"
)

const base = "http://ongsbrasil.com.br"
const first = "http://ongsbrasil.com.br/default.asp?Pag=37&ONG=&Estado=&Cidade=&Tipo=&Atividade=&PageNo=1"
const changing = "default.asp?Pag=37&ONG=&Estado=&Cidade=&Tipo=&Atividade=&PageNo="

type ongList struct {
	LastPage string `goquery:".pagination > li:last-child > a,[href]"`
	lastPage int
	Each     []string `goquery:"td > .text-capitalize p:nth-last-child(2) > a,[href]"`
}

type ongContact struct {
	Nome         string `csv:"Nome" goquery:".text-capitalize"`
	Endereço     string `csv:"Endereço" goquery:".table > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > span:nth-child(1)"`
	Bairro       string `csv:"Bairro" goquery:".table > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2) > span:nth-child(1)"`
	CEP          string `csv:"CEP" goquery:".table > tbody:nth-child(1) > tr:nth-child(3) > td:nth-child(2) > span:nth-child(1)"`
	Cidade       string `csv:"Cidade" goquery:".table > tbody:nth-child(1) > tr:nth-child(4) > td:nth-child(2) > span:nth-child(1)"`
	Estado       string `csv:"Estado" goquery:".table > tbody:nth-child(1) > tr:nth-child(5) > td:nth-child(2) > span:nth-child(1)"`
	País         string `csv:"País" goquery:".table > tbody:nth-child(1) > tr:nth-child(6) > td:nth-child(2) > span:nth-child(1)"`
	Telefone     string `csv:"Telefone" goquery:".table > tbody:nth-child(1) > tr:nth-child(7) > td:nth-child(2) > span:nth-child(1)"`
	NomeFantasia string `csv:"Nome Fantasia" goquery:".table > tbody:nth-child(1) > tr:nth-child(8) > td:nth-child(2) > span:nth-child(1)"`
	Email        string `csv:"E-mail" goquery:".table > tbody:nth-child(1) > tr:nth-child(9) > td:nth-child(2) > span:nth-child(1)"`
	Site         string `csv:"Site" goquery:".table > tbody:nth-child(1) > tr:nth-child(10) > td:nth-child(2) > span:nth-child(1)"`
	CNPJ         string `csv:"CNPJ" goquery:".table > tbody:nth-child(1) > tr:nth-child(11) > td:nth-child(2) > span:nth-child(1)"`
}

func craw(url string, v interface{}) error {
	res, err := http.Get(url)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	charsetReader, err := charset.NewReader(res.Body, "text/html")
	if err != nil {
		return err
	}

	err = goq.NewDecoder(charsetReader).Decode(v)
	if err != nil {
		return err
	}
	return nil
}

func crawList(url string) (ongList, error) {
	var list ongList

	err := craw(url, &list)
	if err != nil {
		return list, err
	}

	pageSplit := strings.Split(list.LastPage, "=")
	list.lastPage, err = strconv.Atoi(pageSplit[len(pageSplit)-1])
	if err != nil {
		return list, err
	}

	return list, nil
}

func main() {

	workURL := first

	list, err := crawList(workURL)
	if err != nil {
		log.Fatal(err)
	}
	ultima := list.lastPage

	contacts := []*ongContact{}

	for page := 1; page <= ultima; page++ {

		workURL = fmt.Sprintf("%s/%s%d", base, changing, page)

		list, err := crawList(workURL)
		if err != nil {
			log.Fatal(err)
		}

		for _, v := range list.Each {
			ongURL := fmt.Sprintf("%s/%s", base, strings.Replace(v, " ", "+", -1))
			var contact ongContact
			if err := craw(ongURL, &contact); err != nil {
				log.Fatal(err)
			}
			// fmt.Println(ongURL, contact)
			contacts = append(contacts, &contact)
		}

	}

	csv, err := os.Create("ongs.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer csv.Close()

	if err := gocsv.MarshalFile(&contacts, csv); err != nil {
		log.Fatal(err)
	}

	// var c ongContact

	// err := craw("http://ongsbrasil.com.br/default.asp?Pag=2&Destino=InstituicoesTemplate&CodigoInstituicao=7559&Instituicao=%20ASSOCIACAO%20DE%20PAIS%20E%20AMIGOSDOS%20EXCEPCIONAIS%20DE%20MOGI%20DAS%20CRUZES", &c)
	// if err != nil {
	// 	log.Fatal(err)
	// }
	// fmt.Println(c)
}

Bolsonaro contra o INPE e Ricardo Felicio “desmente” aquecimento global

Bolsonaro

O nosso excelentíssimo presidente Bolsonaro não para de fazer afirmações sobre assuntos que não domina. Eu me lembro que quando fazia mestrado e minha dissertação era sobre desmatamento causados por linhão eu fui atrás do método utilizado pelo INPE. Na época achei o documento muito porco mas eu não fui retardado de ir no meu orientador e falar que deveria estar tudo errado. Eu fiz a minha própria classificação e comparei com os resultados do INPE (sim, eu perdi meu tempo de mestrado nisso), a diferença não passou de 2% e baixei a minha bola.

É assim que o nosso Presidente deveria fazer. Você primeiro consegue fatos para tentar refutar algo. Isso aqui é método cientifico, o conhecimento é feito de bloquinhos que são removidos caso errados e adicionados se corretos. Você vem falar da credibilidade de um instituto só na base do achismo? Depois vem mais perola que o resultado deveria passar pelo governo primeiro antes de ser divulgado (ministério da verdade? censura? 1984). O cara é tão ignorante que não sabe que os dados de base para isso são públicos e vários países do mundo monitoram o desmatamento da Amazônia. Você cidadão comum pode ir no site da NASA ou do próprio INPE e baixar os fatos que geraram a informação do desmatamento e tentar desmentir.

Então não adianta nada colocar uma empresa privada pra fazer seu mongoloide! Já tem vários pesquisadores/instituições/países refazendo isso o tempo todo! O INPE não quer fazer você ou o país passar vergonha, ele reporta fatos. Politica é com vocês, ciência é com eles. Deixa assim que fica menos feio.

Aquecimento Global

Eu assino o youtube do panico na jovem pan e me aparece um cara falando que aquecimento global é 100% geopolítica. Mermãoooooooo vai tomar no cu. O cara ainda é anunciado como especialista e professor da USP para passar credibilidade. Apesar de ficar PUTO ouvindo tanta merda saindo e o bancada totalmente despreparada fazendo “ooohh” pra cada coisa falada, eu fui atrás do currículo do especialista da USP. O cara é doutor (isso é mérito mesmo, respeita) pela USP e da aula na USP e quando alguém tem doutorado em uma área geralmente já é suficiente para ser chamado de especialista pro povão. Agora na academia pra ser especilista na área é bom que você tenha publicações relevantes sobre o campo de conhecimento em revistas que a QUALIS CAPES classifica como A1 e A2 ou se você não gosta do QUALIS usa o JCR ou fator de impacto.

O especialista, doutor, professor da USP e só tem 11 artigos publicados, em português e sem impacto algum! Resumo do currículo do cara, não é ninguém na comunidade cientifica. Vamos pontuar para ficar claro:

  • Se o seu artigo não é de interesse somente para o país você já errou escrevendo em português. Compartilhamento de descobertas na ciência é através de artigos escritos em inglês.
  • Pesquisador ativo na área geralmente publica um artigo por ano, escrevendo em português dava pra fazer bem mais.
  • Os artigos não possuem impacto nenhum, não mudam n a d a do consenso cientifico.

Para fiz comparativos, eu encerrei minha carreira acadêmica quando percebi que aquilo não era para mim logo após o mestrado. Mesmo sem ser doutor eu tenho cinco artigos publicados, três deles em Inglês. Como bom pesquisador meu lattes nem consta meu último artigo escrito no SiBBr com a Clara Baringo, mas pelo menos eu tenho algum impacto nesses artigos. Eu tenho uma publicação na Plos ONE com a Lilian Dias que é A1, 2.7 de impacto e um na Remote Sensing Environment A1, 6.4 com a Sumaia Vasconcelos.

Com os números acima eu devo ser um especialista nível caneta de ouro comparado com o Ricardo Felicio. Caneta de ouro é um termo usado para dizer que se um autor muito foda na área escrever um artigo ele já é praticamente aceito para publicação. E digo mais, caneta de ouro em desmatamento e fogo na Amazônia. Respeita. O publico leigo que escuta a Jovem Pan cai na conversa fiada de um “especialista” devido a falta de conhecimento no assunto. O que mais uma vez demonstra que o problema do Brasil é educação.

Mas vamos supor que tudo que eu falei é irrelevante e ele levanta uma duvida sobre o aquecimento global. Como eu disse, o conhecimento é feito de bloquinhos que vão se acumulando. Esse acumulo de bloquinhos acabou de passar de 99% para o aquecimento global. A dois dias atras saiu a noticia que o consenso cientifico passou de 99% e que nos últimos 2000 anos nunca houve uma mudança de temperatura tão rápida e extensiva. O que derruba um dos argumentos o famoso “já foi assim”, não, não foi e o artigo publicado na Nature usa 700 variáveis para mostrar que quando essas mudanças aconteceram não eram em escala global.

Deve ser muito fácil esse concurso da USP e eu aqui morrendo de medo de fazer um concurso de medo da minha ignorância ser exposta.

$1,467,635.90

Some clients have weird requests, some bare the legal others are plain illegal and your client will try to convince you with astounding amount of money. Look at this text below of some buyer request:

hello.need only php developer .not apply any designer. here my requiremen :can you create a look a like of another page. for example i want you to create bank site from the original bank of America that i can login and see my bank details such as amount name https://www.bankof******.com/ thats the bank which you will make a clone off the name that should be on the account is “*** *** ******” the figure on the account should be $1,467,635.90 when you login the fake account please i want it to display the name and that value it shoud be very convincing you cant do anything on the clone bank becasue its a clone you can only login see the profile and log out that all, please all other futures on the profile should be responsive the clone website I want it to be Able to creat a new bank account for anyone trying to creat a new accounting i will provide all pages design you only need develop section.

One of tags are #security XD Oh boy you can snap this chance and get paid $100 for a possible fraud charge.

Freelancing

Well, life got tough again and I decided to give a go to freelancing. I found about fiverr at a youtube video where the buyer wanted to a Japanese person to cast hes splatoon game (don’t judge me).

Fiverr differ from any other platform I have seen because sellers don’t need to hunt for a job to do, it is buyers responsibility to find someone suitable for his service. To enable this inverse hunting behavior to happen sellers like me have to build a Gig, which works like a announce of my skills and prices.

example of fiverr gig
How my gig looks in Fiverr

My experience was being great so far. Two months ago I started with the above gig going for $5 the basic package and new seller level. Fiverr help you to start promoting for free your gig at their search result page as new seller/new arrivals. The requirement to climb to level one seller are pretty easy to come.

  • Complete at least 60 days as an active Seller on Fiverr
  • Complete at least 10 individual orders (all time)
  • Earn at least $400
  • Maintain a 4.7 star rating over the course of 60 days
  • 90% Response rate over the course of 60 days
  • 90% Order completion over the course of 60 days
  • 90% On-time Delivery over the course of 60 days
  • Avoiding receiving warnings over the course of 30 days

You and the buyer can create custom offers with new parameters for price, delivery time, etc. For instance earning $400 was really difficult at start, some buyers will look at your score and try to game your price for a service which clearly deserve a better payment but as someone starting you need to earn buyers trust with number of orders delivered.

If you did a good job buyers can also give you a tip besides the review. Buyers will also be asked to leave a review about your service where only Fiverr will have access. This means sellers are reviewed minimum of two times and Fiverr will use this choose where to place you at search results or promotional e-mails.

So if you tired of almost begging for clients while underselling your services and waiting some point system to allow you to continue bidding (yes upwork, workana I’m talking about you) give Fiverr a try. Also if you like my services and want to start with someone already 5 star with amazing reviews consider contacting me.

Contract my services using Fiverr

Rules to maintain sanity developing PHP/Laravel with others

  • Be consistent when placing business logic. If you picked controller stick to it!
  • Be careful with {!! variable !!}, I mean REALLY careful. XSS is so easy to exploit when found
  • Stop bringing data from all places. If using blade template choose to receive data from controller or vue component, both can be confusing
  • Migrations, use migrations! Your big mess called database.sql will not be maintainable over long run
  • If your methods are 900+ LOC something MUST be wrong. Tests? Yes be elitist
  • Vue, Angular, jquery and vanilla js at same application will bring problems even if your team members are at guru skill level
  • Try to stick to default community conventions, stackoverflow is more helpful this way 😉

Practical tests and job interviews

Last year I was dedicated to search good opportunities on-line and almost every position I cared started their hiring process with algorithm testing. Everything was fine until these tests started getting boring, most of them are 30 minutes vs 10 challenges format. If you try to write the amazing code time will get you, when I do awesome run my top solved are seven challenges. At the beginning I got depressed thinking if I was a proper dev.

Below is my algorithm for challenge of writing mean distance software:

package main

import (
	"fmt"
	"math"
)

type point struct {
	x, y float64
}

func distance(a, b point) float64 {
	return math.Sqrt(math.Pow((b.x-a.x), 2.0) + math.Pow((b.y-a.y), 2.0))
}

func mean(n ...float64) float64 {
	sum := 0.0
	for _, v := range n {
		sum += v
	}
	return sum / float64(len(n))
}

func meanDist(points ...point) float64 {
	dsts := []float64{}
	for i := 0; i < len(points); i++ {
		for j := i + 1; j < len(points); j++ {
			dsts = append(dsts, distance(points[i], points[j]))
		}
	}
	return mean(dsts...)
}

func main() {
	points := []point{
		{0.0, 10.0},
		{0.0, 20.0},
		{0.0, 30.0},
	}

	fmt.Println(meanDist(points...))
}

I’m very confident which this kinda of challenge because in my master we used many types of distances and mean is the most used statistic. The reply I got “very good”, you can read this as you’re over 80% of others candidates. How on this damned world of people with formal programming classes I can write good enough code to be called to interviews? Yes, you can be confident of your skills, what your learned cannot be taken from you.