Breno Ferreira's Blog

Da mente de um desenvolvedor para a internet

Como Escrever Um Bom Currículo

Esses dias uma pessoa me pediu para dar uma olhada no seu currículo e dar minha opinião sobre como melhorá-lo. Como essa não foi a primeira vez que alguém me pediu para fazer isso, e tenho um número limitado de keystrokes sobrando, estou escrevendo esse post.

As dicas a seguir são do livro The Google Resume.

Dica número 1: máximo de 2 páginas

Quem recebe muitos currículos odeia ler um currículo muito longo. Além disso, se há mais de 2 páginas, é provável que você tenha colocado mais coisa que o necessário. Se voce conseguir colocar tudo em somente uma página, ótimo! Senão, duas páginas é o suficiente.

Se você já cortou tudo que pode, experimente outro template de currículo. Provavelmente no Word ou Pages (OSX) é possível encontrar templates de currículo com uma formatação boa para economizar espaço. Outra dica é usar serviços que geram um PDF do seu currículo. Voce consegue fazer isso pelo LinkedIn, que irá gerar um currículo baseado nos dados do seu perfil.

Falando em PDF, SEMPRE envie seu currículo nesse formato. Se a pessoa não conseguir ler seu currículo por que não tem o software necessário para abrir o documento, tenha certeza que ela não irá ler. E qualquer pessoa hoje em dia tem um leitor de PDF, seja qual for o dispositivo que ela esteja usando.

Dica número 2: diga o que você fez que teve impacto positivo

Na maioria esmagadora dos currículos que já vi, as pessoas listam suas responsabilidades e o que elas faziam no dia a dia. Se você colocou qual a posição que você ocupava na empresa, suas responsabilidades e tarefas diárias podem ser inferiridas sem muito esforço. Ao invés disso, exponha o que você fez que causou um impacto positivo na empresa ou projeto que você trabalhava, e qual impacto foi esse. Assim, a pessoa que está lendo seu currículo irá saber que você consegue fazer coisas que importam e que você pode fazer a diferença.

Dica número 3: liste no máximo as 3 últimas experiências profissionais relevantes

Seu currículo não precisa ser seu histórico profissional completo. Não há necessidade de listar aquele estágio de 10 anos atrás. Até três itens são suficientes para expor suas competências.

Não é necessário também listar experiências profissionais que não são relevantes e relacionadas com a vaga que você está concorrendo. Não faz muito sentido dizer que você tem experiência como garçom em restaurante se você está enviando seu currículo para uma vaga de gerente comercial.

Dica número 4: conhecimentos relevantes

Da mesma forma que sua experiência profissional, seus conhecimentos técnicos e conquistas acadêmicas também devem ser relevantes. Liste somente sua maior conquista acadêmica (ensino superior, especialização, mestrado ou doutorado), qual a instituição, e período de estudo. Não liste qual colégio você frequentou o ensino médio. Isso não importa.

Dica número 5: diga por que você seria uma boa contratação

Logo depois das suas informações pessoais, é bom você dizer o que faz de você uma boa escolha para aquela vaga. Uma ou duas frases são suficientes. Diga quais suas características pessoais, profissionais e conhecimentos relevantes você possui para que a pessoa lendo seu currículo possa de cara saber se vale a pena continuar lendo.

Seguindo estas regras: um bom currículo segue esse esquema:

  • Suas informações pessoais e contato
  • Uma ou duas frases sobre por que voce se encaixa nessa vaga (Dica 5)
  • Experiência profissional (até 3)
    • Empresa, Cargo, Data de inicio, data de término, descrição (Dica 3)
  • Conhecimentos técnicos e acadêmicos (Dica 4)

Era isso, espero que tenha te ajudado a escrever um currículo melhor e que você seja chamado(a) para uma entrevista.

Tratando Null Values - Parte 2

Veja a parte 1 aqui

Está rolando uma thread no grupo .NET Architects sobre o Null Object Pattern. O assunto começou com uma dúvida sobre como criar mapeamentos do NHibernate com classes que possuem referencias a um Null Object.

Foram dadas algumas respostas, mas a thread continuou com o assunto e dúvidas surgiram sobre: Null Object é Anti-Pattern? Como usar? Non-Nullable References é melhor?

Vou dar algumas opiniões minhas aqui sobre esse assunto.

É Anti-Pattern?

Não acho que seja. Mas pode vir a ser. Patterns são coisas que não devem ser abusadas, e é uma linha bem fina sobre quando é necessário e quando não é.

Algumas reflexões

Em muitas estruturas de dados, é comum o uso de um “Null Object”. Por exemplo:

Listas

Existe um objeto que representa uma lista vazia. Geralmente é chamado de Nil ou Empty.

Árvores

Existe os nós das árvores. Cada um com seu tipo: Node geralmente é o tipo base, e daí existe os tipos Leaf, que representa o último nó de um branch da árvore, e é comum existir o que representa um nó vazio, também chamado de Empty ou Nil. Nós Leaf geralmente usam esse tipo para representar seus sub-nós (que não existem).

Em ambos esses casos, seria melhor ou pior usar um valor null para representar uma lista vazia, ou os filhos de um nó da árvore que não existem? Minha opinião é: seria muito pior. Tornaria o design da API mais feio, e também deixaria o uso da API mais propenso a erros.

Non-Nullable References

A ideia é muito boa, mas na prática, não é tão simples assim de se implementar. Na thread postaram links para quatro posts falando sobre o assunto:

Parte 1 Parte 2 Parte 3 Parte 4

O Eric Lippert (ex-membro do time do C#) também postou em seu blog sobre o assunto:

É uma pena, pois acho que nunca veremos isso no C#.

Uma linguagem que tem suporte a esse recurso é Kotlin. Kotlin é uma linguagem criada pela Jetbrains, que roda sobre a JVM. Essa é uma das poucas linguagens que eu já vi que se preocupou com isso desde o início.

Option type

postei aqui sobre o assunto. E, como a thread é do .NET Architects, eu e o Abner (trabalha comigo na Lambda3) criamos um projeto no Github com uma implementação do tipo Option em C#.

Alguns exemplos:

1
2
3
4
5
6
7
8
9
10
11
12
13
var pessoa = new Pessoa
{
    Nome = "Robb Stark"
};

var optionPessoa = Option.Create(pessoa);

var logradouro = optionPessoa
                        .Map(p => p.Endereco)
                        .Map(e => e.Cidade)
                        .GetOrElse("Não informado");

logradouro.Should().Be("Não informado");
1
2
3
var option = Option.From(1);

option.Should().Be(Option.From(1));
1
2
3
var option = Option.From<string>(null);

option.Should().Be(Option.None<string>());

Inclusive com suporte (inicial) a Linq.

1
2
3
4
5
6
7
8
9
10
var pessoa = new Pessoa
{
    Nome = "Robb Stark"
};

var res = (from p in Option.From(pessoa)
          from e in p.Endereco.ToOption()
          select e.Cidade).GetOrElse("Sem endereco");

res.Should().Be("Sem endereco");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var pessoa = new Pessoa
{
    Nome = "Robb Stark",
    Endereco = new Endereco
    {
        Cidade = "Winterfell"
    }
};

var res = from p in Option.From(pessoa)
          from e in p.Endereco.ToOption()
          select e.Cidade;

res.Should().Be(Option.From("Winterfell"));

O que acham? “Linq-To-Option” ficou legal?

Lição Aprendida Em Cenário De Alta Escalabilidade

Atualmente na Lambda3, estou trabalhando em um projeto que demanda alta performance e escalabilidade, pois terá uma demanda na casa de alguns milhares de usuários simultâneos (em pior caso).

A aplicação, hospedada no Windows Azure, é feita em ASP.NET MVC, que tem um suporte razoável para execução de requests de maneira assíncrona.

1
2
3
4
5
6
public async Task<ActionResult> Index(int id)
{
    var model = await <async expression>;

    return View(model);
}

Então, qualquer chamada que possa demorar, em todo o ciclo de vida do Request, é feito de maneira assíncrona. Chamadas a Banco de Dados (usamos o Azure Table Storage), requisições remotas, entre outras, são todas feitas utilizando o async/await pattern do C#.

Por que isso foi feito? Para não bloquera threads do Thread Pool do IIS. Se toda vez que alguma operação de IO bloqueasse a execução do Request, a thread ficaria bloqueada até o término da operação. E como vamos ter milhares de usuários simultâneos, isso poderia levar à todas as threads disponíveis no Thread Pool do IIS estarem ocupadas, e a aplicação iria parar de responder. Se as operações são executadas de maneira assíncrona, as threads são liberadas assim que a operação inicia, e depois que ela termina, outra thread disponível no Thread Pool é usada para continuar a execução. Assim, nenhuma thread fica bloqueada por muito tempo. Essa é uma das maneiras mais simples (mas não é tão simples assim) de resolver o problema. Node.JS por exemplo, em toda sua API base, só usa operações assíncronas de IO.

Quando conseguimos chegar em um estágio razoavelmente estável da aplicação, começamos a fazer testes de carga, rodamos alguns cenários: com algumas centenas de usuários simultâneos e poucas instâncias (umas 2 ou 3) executando a aplicação. Os resultado foram bons. Conseguimos responder 200 requests simultâneos tranquilamente, com um tempo de resposta razoável, na média de 1-2 segundos.

Mas, quando subimos para 1000 usuários simultâneos e 10 instâncias, os resultados foram muito ruins. A aplicação começou a parar de responder, e tinhamos uma média de tempo de resposta de 10 segundos! Inaceitável.

Por que? Tinhamos feito tudo bonito, async e await para todos os lados. O uso de CPU e de memória nas VMs estava baixo. O que estava acontecendo?

Configuramos o New Relic para monitorar a aplicação e o que vimos é que tinha um método do ASP.NET que estava demorando muito para responder: System.Web.HttpApplication.BeginRequest. Uma rápida pesquisa no Google nos levou a algumas possibilidades, e uma delas, era de que esse método estava bloqueando enquando o ASP.NET esperava threads serem liberadas para processar o request.

Durante a caçada ao problema, percebi uma coisa estranha. Tinhamos um mecanismo de gravação de logs na aplicação. Como eram gerados uma quantidade razoável de logs, as vezes durante um único request, eles eram escritos de maneira assíncrona também. Dando uma olhada na implementação de um TraceListener customizado nosso, vi que no método Flush, esperavamos todas as operações de escrita no log terminarem:

1
2
3
4
5
public override void Flush()
{
    Task.WaitAll(escritasNoLog);
    base.Flush();
}

Até aí tudo bem. Como os métodos de escrita de log seguiam o esquema “fire-and-forget”, o método Flush não fazia muita diferença. Só se em alguma parte do sistema fosse necessário esperar a escrita de operações de Log. O que foi surpresa para mim, é que na configuração de logs da aplicação, a propriedade autoflush estava ligada!

1
2
3
4
<system.diagnostics>
    ...
    <trace autoflush="true" indentsize="4" />
</system.diagnostics>

Ou seja, cada vez que era executado um Trace.Write("...") na aplicação, a execução bloqueava esperando a escrita do log terminar. Como isso ocorria com uma certa frequencia, basicamente em todos os requests a execução bloqueava por um determinado período de tempo. Desligada a opção autoflush, feito o deploy novamente, e após a execução dos mesmos testes de carga, o tempo de resposta ficou na média de 1-2 segundos e a aplicação estava respondendo 6 vezes mais requisições durante a execução inteira do teste de carga.

Antes da mudança de configuração, tinhamos um tempo médio de resposta a cada requisição de 10 segundos, e durante o teste de carga (10 minutos), eram executados entre 15 e 20 mil cenários (todo um fluxo de teste com várias interações no sistema). Após desligar o autoflush, o tempo médio de resposta caiu para 1-2 segundos, e era executado, nos mesmos 10 minutos do teste, entre 80 e 90 mil cenarios. Todas as vezes usamos 10 instâncias de VMs do tamanho medium do Windows Azure. Os agentes de execução dos testes de carga rodavam localmente em um servidor no nosso escritório em SP.

Conclusão

Vimos na prática que bloquear a execução de threads do web server por muito tempo, em um ambiente com uma enorme quantidade de usuários concorrentes vai ser um grande golpe na performance do sistema. É muito bom ver que o pessoal que desenvolve frameworks já está ligado nisso e que isso hoje já não é tão dificil de resolver. Já vi soluções muito boas em frameworks conhecidos nas plataformas .NET (ASP.NET MVC), Node (Express), Scala (Play+Akka, Finagle+Finatra). Esses são alguns que eu conheço. Não sei dizer como andam as coisas no mundo Rails e Django por exemplo.

Claro que nosso cenário ainda não chega perto do problema do Twitter, Facebook, Amazon, etc.. Esses casos são bem mais complexos. Mas, no nosso cenário, bastando a execução assíncrona de IO, conseguimos escalar bem a aplicação para uma quantidade na casa dos milhares de usuários simultâneos.

Tratando Null Values Like a Boss Com O Tipo Option

Um dos pesadelos de todo desenvolvedor, é uma NullPointerException. Tratar objetos que podem conter um valor, ou não, é chato, e muito propenso à erros. Quem nunca esqueceu de fazer um check contra valores nulos?

1
if(value == null) { throw new InvalidArgumentException() }

ou ainda

1
2
var pessoa = pessoas.find(x => x.Nome == "Breno")
pessoa.AumentarSalario(100) // NullReferenceException

Seria bom se houvesse alguma maneira de evitar esse tipo de problema de Exceptions serem lançadas por que o desenvolvedor esqueceu de tratar um valor que possivelmente veio nulo.

Desde que comecei a me aventurar pelo mundo de linguagens funcionais, percebi que lá, eu não me preocupava com isso. E isso se devia ao fato de que eu usava um tipo de dados bem interessante: o tipo Option.

1
2
3
4
trait Option[+T] {
  def value:T
  def hasValue:Boolean
}

Como voce pode ver, o tipo Option simplesmente encapsula um valor possivelmente nulo. E existem os tipos concretos, chamados Some e None. Abaixo a declaração de ambos.

1
2
3
4
5
6
7
8
case class Some[T](val value:T) extends Option[T] {
  def hasValue = true
}

case object None extends Option[Nothing] {
  def value = throw new Exception("No value")
  def hasValue = false
}

E também temos um objeto Option com o método apply que cria um valor do tipo Option com base no valor passado como parâmetro:

1
2
3
object Option {
  def apply[A](x: A): Option[A] = if (x == null) None else Some(x)
}

Assim, podemos criar valores do tipo Option da seguinte maneira:

1
2
val ten = Some(10)
println(ten.value) //10
1
2
val none = None
println(ten.value) //throws Exception

Como voces podem ver, o encapsulamento do valor é feito, mas ainda sim, podemos cair em uma Exception no caso de tentarmos acessar o value do Option. Quer dizer então que o Option não serve para nada? Não é bem assim.

O poder verdadeiro do Option está em suas higher-order functions map e flatMap. Na definição da trait Option, temos a declaração dos métodos:

1
2
3
4
5
def flatMap[B](f: T => Option[B]):Option[B] =
  if(hasValue) f(value) else None

def map[B](f: T => B):Option[B] =
  flatMap(_ => Option(f(value)))

Qual a útilidade desses métodos? Simples:

  • Ter acesso ao valor armazenado no Option, se houver um;
  • Fazer combinações de valores do tipo Option

Como assim?

1
2
3
4
val ten = Some(10)
val timesTwo = ten.flatMap(x => Some(x*2))

timesTwo.map(x => println(x)) //20

Como vimos, com os métodos flatMap e map podemos acessar o valor contido dentro do Option, também é possível transformar o valor em um outro valor do tipo Option. No exemplo acima, criamos um Some(10) e depois o transformamos, multiplicando o valor por 2. Assim, obtemos um Some(20). Logo em seguida, usamos o método map para chamarmos o método println que imprime o valor na tela. E o legal é que, caso em alguma chamada a map ou flatMap, apareça um valor null, o resultado vai ser um None. E qualquer chamada a uma dessas funções sobre um valor None, nada irá acontecer, e assim, nenhuma NullReferenceException será lançada. Legal não é?

Também é possível obter o valor contido dentro do Option através de Pattern Matching:

1
2
3
4
5
6
val ten = Some(10)
ten match {
  case Some(x) => println(x)
  case None => println("Vazio")
}
//10
1
2
3
4
5
6
val none = None
none match {
  case Some(x) => println(x)
  case None => println("Vazio")
}
//Vazio

Nos exemplos acima, em um caso, o valor é um Some(10), então ele cai no primeiro caso, e imprime o valor 10. No segundo caso, como é um None, ele cai no segundo caso, e imprime “Vazio”.

Um exemplo mais “mundo real” seria:

1
2
3
val joao = pessoas.find(p => p.Nome == "João")//Retorna um Option[Pessoa]
val salarioDoJoao = joao.map(p => p.Salario) //Retorna um Option[Double]
println(salarioDoJoao.getOrElse(0.0))

No exemplo acima, temos uma lista de pessoas pessoas, e chamamos o método find para tentarmos achar dentro dessa lista, uma pessoa chamada “João”. Como o João pode não existir dentro da lista, ele retorna um Option[Pessoa]. Daí, pegamos o salário do possível valor do joao com a funcão map. No final, imprimimos o valor do salário. Mas, como o resultado final pode ser um None, chamamos mais uma função interessante chamada getOrElse. O que essa função faz é simples.

1
2
3
4
5
def getOrElse[B >: T](default:B) =
  this match {
    case Some(x) => x
    case None => default
  }

Como podemos ver, ela faz um Pattern Matching sobre o próprio valor, e caso ele seja um Some[T], retornamos o próprio valor, caso seja um None, retornamos o valor default passado como parâmetro.

Então, no exemplo do João acima, caso o João exista na lista, o seu salário será impresso na tela, caso não, será impresso o valor 0.0.

O legal do Option é que através da definição do seu tipo, e de suas higher-order functions, torna-se difícil uma NullReferenceException ser lançada. Toda vez que alguma função possa retornar um valor nulo, basta retornar um valor do tipo Option[T], ao invés de simplesmente um valor do tipo T. A partir daí, é só usar as funções map e flatMap, que elas se encarregam de tratar os casos onde o valor for nulo, propagando o None por toda a cadeia de chamadas.

Meu Primeiro Post - Um Meta-post

Olá

Esse já deve ser meu terceiro ou quarto primeiro post em um blog que inicio, depois de tentativas fracassadas em escrever sobre algum assunto, que na época julgava interessante. E, já passei um tempo matutando sobre o que escrever no primeiro post dessa nova empreitada. É bem difícil decidir o assunto do primeiro post, como escrevê-lo e qual mensagem passar. Então, vai lá minha nova tentativa.

Demorei muito para finalmente conseguir passar do primeiro parágrafo deste blog. Entre dúvidas de assuntos e uma certa resistência em digitar as primeiras palavras, acabava desistindo. Até que um dia, me deparei com um livro que me fez acordar para alguns problemas que estava enfrentando.

The War of Art

Neste livro, o autor fala sobre alguns assuntos, e um dos principais temas é sobre Resistência, e como ela acaba nos bloqueando de fazer o que a gente quer ou precisa fazer. Abaixo segue dois trechos traduzidos do livro:

Resistência infalivelmente vai apontar para o Norte – isso significa aquela ação ou tarefa que ela quer evitar que façamos. Podemos usar isso. Podemos usá-la como uma bússola. Nós podemos navegar por Resistência, deixando-a nos guiar para aquela ação ou tarefa que devemos seguir antes de todas as outras.

Regra de ouro: Quanto mais importante uma tarefa ou ação for para a evolução da nossa alma, mais Resistência iremos sentir em perseguí-la.

Então, como voces viram pelo primeiro parágrafo deste post, eu me identifiquei bastante por essa idéia. É algo que eu já senti algumas vezes na vida quando queria começar a fazer algo diferente.

E este post é o resultado de uma tentativa de mudança de hábito após começar a ler esse livro. Aqui voce provavelmente vai encontrar coisas sobre diversos assuntos, desde tecnologia, alguns ocasionais reviews de livros que li, opiniões minhas sobre algum assunto, entre outras coisas. Espero ter sucesso! Desejem-me sorte.

Abraços

Breno

js.src = "//connect.facebook.net/en_US/all.js#appId=268611913287117&xfbml=1";