segunda-feira, 12 de outubro de 2015

Adaptação do tema One Dark para NetBeans

Logo que baixei o editor Atom fiquei bem impressionado com o tema de cores padrão, chamado "One Dark".

Ele parece uma espécie de variação do tema Wombat.

O tema padrão do Atom tem um aspecto azulado bem suave, que torna a leitura bastante fácil. Os olhos agradecem.


Editor de texto Atom com o tema One Dark

Achei o tema One Dark tão bacana que resolvi fazer uma adaptação para o NetBeans, que é meu IDE favorito.

Nem tudo foi possível fazer exatamente igual, mas no geral o NetBeans, que é sofrível no aspecto "temas", ficou bem mais agradável depois dessa adaptação.

Chamei o tema de DuliDark.


Tema DuliDark no NetBeans


Download


Faça o download do tema DuliDark para NetBeans aqui.

Instalação do tema


Para importar o tema no NetBeans, vá em Ferramentas -> Opções -> Importar... -> Procurar... e selecione o arquivo ZIP baixado.

Se gostar, compartilhe!

terça-feira, 31 de março de 2015

Obtendo informações sobre seu hardware no Linux

Nem sempre é tão fácil verificar o fabricante ou o tipo e especificações do hardware que você tem no seu computador com Linux.

Aqui vão algumas dicas de excelentes programas que executam a tarefa muito bem, de fora bem simples pela linha de comando:

1 - inxi


Embora pouco conhecido, o INXI é excelente script para exibir, com formatação muito bem organizada e legível, as informações sobre o seu hardware.

Aqui estão as instruções para instalar na sua distro.



Alguns comandos do Inxi:

Informações resumidas:

inxi

Informações básicas:

inxi -b

Obtendo as partições do HD:

inxi -p

Informações sobre o tamanho e modelo do HD:

inxi -D

Informações sobre a placa de som e placa de vídeo:

inxi -AG

Para obter o relatório completo (Full):

inxi -F

Outros programas simples e interessantes


2 - lscpu


Fornece informações sobre sua CPU e unidades de processamento.

3 - lshw


Fornece informações detalhadas ou resumidas sobre diversos itens de hardware, como cpu, memória, disco, interfaces de rede etc.

4 - lspci


Esse talvez seja o mais conhecido e comum. Lista todas as interfaces PCI e detalhes sobre os dispositivos que estão conectadas a elas.

5 - lsscsi


Lista os dispositivos (discos rígidos / drives óticos) do tipo scsi/sata.

6 - lsblk


Lista todos os dispositivos de blocos (HDs) e suas partições.



7 - df


Dá informações sobre as particões, pontos de montagem e espaço disponível/usado.

É muito comum o uso com as opções df -Th (tipo da partição e tamanho em bytes/megabytes/gigabytes/etc e não em blocos).

8 - mount


Sem nenhum parâmetro, o comando irá exibir os pontos atualmente montados.

9 - free


Exibe o total de memória RAM usada e livre.

É comum usar o comando com o parâmetro -m, para exibir as quantidades em megabytes.

sábado, 28 de fevereiro de 2015

Zoneminder no CentOS 6 - método fácil com zmrepo

Sobre o ZoneMinder


Nesse post foi demonstrado como instalar o ZoneMinder no Ubuntu Server. Se quiser informações sobre a instalação das câmeras IP e sobre o que é o ZoneMinder, abra o link já referido.

Instalando no CentOS


Segue agora o passo a passo para instalar o poderoso sistema de vigilância no CentOS 6 (Versão 6.6, arquitetura x86_64).

Ainda não há método estável para instalar no CentOS 7 por meio de repositório. Portanto, use o CentOS 6 por enquanto.

OBS: Todos os comandos aqui descritos foram executados como root.


Instale o CentOS.

Após o primeiro boot, habilite a interface de rede e defina o IP da máquina.
nano /etc/sysconfig/network-scripts/ifcfg-eth0
Para IP dinâmico, simplesmente altere a opção ONBOOT conforme abaixo e reinicie o sistema:
ONBOOT="yes"
Para IP estático, faça as alterações necessárias para ficar assim:
DEVICE=eth0
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.0.100
NETMASK=255.255.255.0
GATEWAY=192.168.0.1
DNS1=8.8.8.8
DNS2=208.67.222.222
Para evitar lentidão ao conectar via ssh:
nano /etc/ssh/sshd_config
E deixe as seguintes opções conforme abaixo:
UseDNS no
GSSAPIAuthentication no
Reinicie os serviços de rede e do servidor ssh:
service network restart
service sshd restart
Atualize o sistema
yum update -y
Desabilite o SELINUX
setenforce 0

nano /etc/selinux/config

SELINUX=disabled

Instale o repositório Zmrepo:
yum install wget


wget http://zmrepo.connortechnology.com/el/6/i386/zmrepo-6-4.el6.noarch.rpm


yum install --nogpgcheck zmrepo-6-4.el6.noarch.rpm -y
Certifique-se de que o repositório foi corretamente instalado:
yum repolist
Instale o zoneminder:
yum install zoneminder -y
Agora você deve completar os passos descritos no aquivo abaixo para concluir a instalção:
less /usr/share/doc/zoneminder-*/README.CentOS
Em suma, você deverá fazer o seguinte. Inicie o serviço mysqld:
chkconfig mysqld on

service mysqld start
Agora conclua a instalação segura do mysqld. Quando for perguntada a senha atual do usuário "root", aperte a tecla Enter, pois ainda não há uma senha configurada (por se tratar de uma nova instalação). Quanto às outras opções, pode responder sempre o padrão:
/usr/bin/mysql_secure_installation
Agora você deverá criar o banco de dados para o ZoneMinder usar. Ao ser perguntado, digite a senha de root que você definiu no passo anterior:
mysql -uroot -p  

create database zm;

grant select,insert,update,delete,lock tables,alter on zm.* to 'zmuser'@localhost identified by 'zmpass';

quit;

mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql

mysqladmin -uroot -p reload
(OBS: Se você escolheu outros usuário e senha para o bando de dados zm no item anterior, então edite o arquivo /etc/zm.conf e faça os ajustes em ZM_DB_USER e ZM_DB_PASS)

Ajuste o timezone da sua localidade nas configurações do php, exemplo:
nano /etc/php.ini
date.timezone = America/Sao_Paulo
Habilite a interface web do ZoneMinder (ela vem desabilitada por padrão):
nano /etc/httpd/conf.d/zoneminder.conf
Apague ou comente (com um #) a linha "Deny from all # DELETE THIS LINE".

Alguns ajustes para evitar mensagens de erro do apache (altere conforme suas configurações):
echo "ServerName localhost" > /etc/httpd/conf.d/fqdn.conf
Inicie o servidor web (apache) e configure-o para iniciar automaticamente a cada boot:
service httpd start
chkconfig httpd on
Abra as portas necessárias no firewall (iptables) do CentOS:
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT

service iptables save

iptables -L -v
Ou, se quiser fazer por interface gráfica:
yum install system-config-firewall
system-config-firewall
# Personalizar
# Marque as opções
WWW (HTTP)
WWW Seguro (HTTPS) (caso você queira habilitar acesso criptografado)
Fechar
Ok

Reinicie o sistema
reboot
ou
init 6
No seu navegador, aponte para http://<ip da máquina>/zm

Se quiser habilitar SSL (navagação segura):
yum install mod_ssl openssl
Gere a nova chave (auto-assinada) e novo certificado:
openssl genrsa -out ca.key 1024
openssl req -new -key ca.key -out ca.csr
openssl x509 -req -days 730 -in ca.csr -signkey ca.key -out ca.crt
Mova para os lugares corretos
mv -f ca.crt /etc/pki/tls/certs/localhost.crt
mv -f ca.key /etc/pki/tls/private/localhost.key
Reinicie o servidor apache (web)
service httpd restart
No seu navegador, aponte para https://<ip da máquina>/zm

ALGUMAS DICAS DE PERFORMANCE


O servidor ZoneMinder não pode ficar com um "load" muito alto. Se isso ocorrer, você terá instabilidade e perderá alguns "frames" de imagens.

A seguir algumas dicas para tentar melhorar a performance.

1. Para sistemas de arquivos Ext4, adicione as seguintes opções no /etc/fstab:
noatime,nodiratime,commit=120,data=writeback

Exemplo
UUID=ba4acd10-ae6d-465c-82fe-9752017480ee /var/lib/zoneminder     ext4    defaults,noatime,nodiratime,commit=120,data=writeback        0 2
2. Habilite nas opções do ZoneMinder o CPU_EXTENSIONS, para obter vantagem das extensões SSE2/SSE3 do processador.

3. Habilite FAST_IMAGE_BLENDS. Esse tipo de blend é extremamente rápido e não envolve multiplicação ou divisão, o que pode impactar a performance.

4. Desabilite COLOUR_JPEG_FILES a não ser que realmente precise disso. Essa opção converte imagens em grayscale para cor antes de armazená-las como jpegs. Isso impacta a performance e usa mais espaço no seu disco, então o melhor é deixar desabilitado.

5. Desabilite CREATE_ANALYSIS_IMAGES se você usa motion detection com Blob, mas não precisa das imagens de análise.

6. Tenha certeza que desabilitou RECORD_DIAG_IMAGES.

7. Para câmeras locais (não IP): Habilite ZM_V4L_MULTI_BUFFER se puder. Tente fazer correspondência entre a paleta de caputra (Capture Palette) e o colorspace alvo (Target Colorspace), para evitar a necessidade de conversão de formato.

8. Se estiver usando Motion Detection (Modect) para as câmeras, quanto menos zonas, mais rápido será o sistema. Antes de ficar criando zonas, pense se realmente você tem necessidade de mais de uma zona.

9. Experimente usar a opção 8 bit grayscale nas câmeras (Target Colorspace), em vez de cores 24 ou 32 bits. Além de as imagens ficarem menores, o processamento e análise delas será mais rápido. Você realmente precisa registrar imagens coloridas? Faça opção consciente.

10. Quantos quadros por segundo (frames per second - FPS) você realmente precisa nas câmeras? Pense nisso. Não há qualquer razão para caputar o vídeo a 20 fps se você não tem qualquer necessidade específica para isso. 5 fps são suficientes para a maioria dos casos e produz bons vídeos.

11. Use a libjpeg-turbo. A instalação pelo método descrito neste post se encarregará disso.

12. Ajuste as opções "Reference Image Blend %ge" e "Alarm Reference Image Blend %ge" de cada monitor apropriadamente. Quanto à primeira, use 6.25% para câmeras internas e 12.5% para externas. Quando à segunda, experimente deixar todas as câmeras em 6.25%. Trata-se de nova funcionalidade da versão do ZoneMinder 1.26.5. Antes de ajustar essa opção alguns eventos estavam ficando quase infinitos (disparava o alarme do modect e não parava mais de gravar).

ATENÇÃO


As opções Maximum FPS e Alarm Maximum FPS são apenas para câmeras analógicas. Não use essas opções para câmeras remotas IP, caso contrário você terá falhas na visualização "ao vivo" e durante as gravações dos eventos. A implementação para câmeras remotas ainda não foi realizada.

quarta-feira, 6 de agosto de 2014

Como realmente desligar o Windows 8

Windows é Windows. Sempre com comportamentos estranhos e pouco transparentes para os usuários. Com o desligamento do seu computador não é diferente.

Toda vez que você clica em desligar, na verdade o Windows 8 não é realmente finalizado. Ele apenas entra em hibernação. É a forma que o Windows encontrou para iniciar mais "rápido", haja vista que em tese é mais rápido o sistema operacional retornar da hibernação do que ser iniciado totalmente do zero novamente.

No entanto, há ocasiões em que você quer realmente desligar o sistema operacional, seja para tirar o HD da sua máquina, seja para fazer um backup integral do seu HD, seja porque você quer realmente desligar e pronto. Ultimamente, aliás, com a nova sistemática da venda de computadores com o Windows 8 previamente instalado - sem que venha com o computador qualquer mídia para reinstalação do sistema -, é totalmente recomendável realizar um clone do seu HD (por exemplo com o Clonezilla ou qualquer outro programa do tipo), para eventual restauração do Windows 8 quando ele der problema (algo não muito difícil de ocorrer...).

Ocorre que se você não desligar realmente a máquina você não conseguirá efetuar a clonagem do HD, pois esses programas não conseguirão clonar um HD com o sistema operacional hibernado nele.


Para desligar realmente o Windows 8, a forma mais rápida e menos traumática que encontrei é a seguinte: abra o terminal do Windows (digite cmd na busca de aplicativos - você poder apertar a tecla 'windows' e começar a digitar) e execute o seguinte comando no terminal:

shutdown /s /t 0

O parâmetro /s significa shutdown (desligar) em contraposição a reboot (reiniciar - /r) e o parâmetro /t 0 significa para executar imediatamente (ou seja, em zero segundo).

Se você quiser desligar em definitivo a hibernação (ou 'Fast Startup', como eles chamam), digite 'energia' (sem as aspas) na pesquisa do Windows 8 (basta apertar a tecla "windows" do seu teclado e começar a digitar).

Clique na linha que irá aparecer com o escrito 'Configurações' no painel direito. Aparecerá a entrada 'Opções de Energia'. Clique nessa opção.

O painel de configurações de energia aparecerá. Clique em "Escolher a função dos botões de energia". Clique em "Alterar configurações não disponíveis no momento". Agora é só desabilitar a opção "Ligar inicialização rápida (recomendado)" e depois "Salvar alterações".

Pronto, agora o Windows 8 fará um desligamento genuíno sempre que você clicar normalmente em desligar.

Mais informações, clique aqui.

terça-feira, 1 de julho de 2014

Como encontrar arquivos modificados ou acessados em determinada data

Já se viu em situação em que editou um arquivo ou armazenou alguma informação e não lembra de nenhum jeito o nome do arquivo ou onde ele foi gravado, mas lembra que foi "ontem" ou "nessa semana"?



O comando find possui forma bastante eficiente de localizar os arquivos modificados, acessados ou que tiveram permissões alteradas a partir de certa data.

O segredo é usar testes do find com os parâmetros -newerXY and ! -newerXY.

Vamos aos exemplos.

Para localizar todos os arquivos modificados em 29/06/2014:

find . -type f -newermt 2014-06-29 ! -newermt 2014-06-30

Nesse caso o 'm' corresponde a "modification  time of the file reference" e o 't' corresponde a "reference is interpreted directly as a time".

O ponto '.' refere-se à pasta atual. O comando procurará arquivos na pasta atual e subpastas.

Para encontrar todos os arquivos acessados em 29/09/2008:

find . -type f -newerat 2008-09-29 ! -newerat 2008-09-30

Aqui o 'a' significa "the access time of the file reference".

Para encontrar os arquivos que tiveram permissões alteradas na mesma data acima:

find . -type f -newerct 2008-09-29 ! -newerct 2008-09-30

Aqui o 'c' corresponde a "inode status change time of reference".

Para maiores informações, consulte o manual do find (man find).

segunda-feira, 2 de junho de 2014

Temas mais agradáveis no Ubuntu

Se você é como eu e não gosta das cores do tema padrão que vem no Ubuntu, a boa notícia é que há solução fácil para isso.

Os dois temas que menos alteram o visual atual e, ao mesmo tempo, mais agradáveis para substituir o tema padrão do Ubuntu são, sem dúvida, o Ambiance Colors e Radiance Colors.

Pelo menos você pode sair um pouco do mesmo laranja de sempre do Ubuntu. Abaixo alguns exemplos de cores mais suaves sem perder o aspecto profissional da aparência da área de trabalho.




Para instalar os temas, basta adicionar o repositório dos desenvolvedores e mandar brasa na instalação com o apt-get.

sudo add-apt-repository ppa:ravefinity-project/ppa
sudo apt-get update
sudo apt-get install ambiance-colors radiance-colors

Para escolher entre os novos temas instalados, você pode usar o Unity Tweak Tool que, aliás, é excelente para mudar diversos outros aspectos visuais e comportamentais da sua área de trabalho.

Como ele está nos repositórios oficiais, basta executar:

sudo apt-get install unity-tweak-tool

Para usar ícones que correspondam às novas cores do tema, você pode instalar o conjunto de ícones Humanity Colors dos mesmos desenvolvedores:

sudo apt-get install humanity-colors

(lembre-se que a instação dos ícones só irá funcionar se você já adicionou o repositório ravefinity conforme comando anteriormente descrito)



Os ícones também poderão ser alterados pelo Unity Tweak Tool.

Bom proveito!

quinta-feira, 29 de maio de 2014

Boas práticas em programação [Parte 02 de 02]

Este post é continuação da [Parte 01 de 02]. Recomenda-se a prévia leitura daquele post antes de se aprofundar neste post.

Objetos e Estrutura de Dados


Esconder implementação não é apenas uma questão de adicionar uma camada de funções entre as variáveis. Esconder implementação é uma questão de abstração. Uma classe não deve expor suas variáveis por meio de métodos de acesso e mutação (getters e setters). Em vez disso, deve expor interfaces abstratas que permitem aos seus usuários a manipulação dos seus dados, sem que os usuários sejam obrigados a conhecer sua implementação.

Considere os seguintes exemplos:

public interface Vehicle {
    double getFuelTankCapacityInGallons();
    double getGallonsOfGasoline();
}

public interface Vehicle {
    double getPercentFuelRemaining();
}

O primeiro exemplo se comunica usando termos concretos, enquanto o segundo se comunica por meio da abstração da percentagem de combustível. A segunda forma é preferível, pois o ideal é não expor os detalhes dos dados. É melhor expressar a informação em termos abstratos. Isso não é alcançado pelo mero uso de interfaces acompanhadas de getters/setters. É preciso dispensar muita atenção quanto à melhor forma de representar a informação que um objeto contém. A pior escolha é simplesmente ficar adicionando getters e setters sem um critério cuidadosamente escolhido.


Assimetria entre Dados e Objeto


Objetos escondem seus dados por trás de abstrações e expõem funções que operam sobre os dados. Estrutura de dados (data structure) expõem sua informação e não possuem funções substanciais ou relevantes. O tipo clássico de estrutura de dados é uma classe com variáveis públicas e nenhuma função. Vê-se, portanto, que 'objetos' e 'estrutura de dados' são conceitos bastante opostos.

Programação procedimental (código que usa estrutura de dados) torna fácil a adição de novas funções sem que seja preciso alterar a estrutura dos dados. Mas a adição de novas estruturas de dados é difícil porque todas as funções existentes necessariamente precisarão sofrer alterações.

Já a programação orientada por objetos torna fácil a adição de novas classes sem a necessidade de alteração das funções existentes. Mas torna difícil a adição de novas funções porque muitas classes precisarão ser alteradas. Assim, tarefas difíceis para a programação orientada por objetos são fáceis na procedimental, e tarefas difíceis na procedimental são fáceis na orientada por objetos.

Em qualquer sistema complexo haverá momentos em que orientação por objetos será mais apropriada e momentos em que programação procedimental será a melhor opção. Por isso, é um mito afirmar que "tudo é um objeto".  Muitas vezes você realmente quer apenas estrutura de dados simples com linguagem procedimental operanto sobre elas. Nem sempre haverá a necessidade de adicionar uma camada orientada por objetos.

A Lei de Demeter


Objetos escondem seus dados e expõem operações. Logo, objetos não devem expor sua estrutura interna por meio de métodos de acesso (getters). A noção fundamental da Lei de Demeter é que um objeto deve saber o mínimo possível a respeito da estrutura ou propriedade de qualquer outra coisa (outras classes e inclusive subcomponentes).

Esses são os princípios da Lei de Demeter:

- Cada unidade deve ter conhecimento limitado sobre as outras unidades: somente sobre unidades intimamente relacionadas com a unidade atual.

- Cada unidade deve conversar somente com os "amigos"; não converse com estranhos.

- Somente converse com seus amigos imediatos.

A criação de estrutura híbridas, ou seja, que são metade objeto e metade estrutura de dados, são um convite para a violação da lei de Demeter. Esses tipos híbridos possuem funções que fazem coisas significativas e também variáveis públicas ou então métodos públicos que acessam e modificam variáveis privadas (que, por isso mesmo, terminam sendo acessadas como se fossem públicas). Esse tipo híbrido termina facilitando o uso de suas variáveis por funções externas da mesma forma que a programação procedimental usaria uma estrutura de dados, violando claramente a lei de Demeter. Tais tipos híbridos devem ser evitados.

Registro Ativo (Active Record)


Registros Ativos são uma forma especial de Objetos para Transferência de Dados (Data Transfer Objects). São estruturas de dados com variáveis públicas ou métodos públicos de acesso às variáveis privadas. Em geral, são traduções de tabelas de banco de dados ou de outros tipos de fontes de dados.

É muito comum desenvolvedores tratarem esse tipo de estrutura de dados como se fossem objetos inserindo métodos de regras de negócios (business rules) neles. Ocorre que isso é exatamente o que os transforma no tipo híbrido acima referido. A solução é tratar o Active Record como se fosse apenas uma estrutura de dados e criar objetos separados que contenham métodos e escondam seus dados internos (os dados internos provavelmente serão apenas instâncias do Active Record).

Manipulação de Erros


Erros são inveitáveis. Por isso, o manuseio de erros é algo importante. Porém, se a manipulação do erro obscurecer a lógica do programa, é porque está sendo feita de forma pobre ou inapropriada.

A técnica de retornar "códigos de erros" (error codes) era necessária na época em que não havia as exceções (exceptions). O problema dessa abordagem é que obriga o usuário da função a lidar com os erros de forma imediata, torando-se tarefa de fácil esquecimento, além de obscurecer a implementação do código-fonte.

Compare os dois exemplos abaixo.

public class DeviceController {
    ...
    public void sendShutDown() {
        DeviceHandle handle = getHandle(DEV1);
        // Check the state of the device
        if (handle != DeviceHandle.INVALID) {
            // Save the device status to the record field
            retrieveDeviceRecord(handle);
            // If not suspended, shut down
            if (record.getStatus() != DEVICE_SUSPENDED) {
                pauseDevice(handle);
                clearDeviceWorkQueue(handle);
                closeDevice(handle);
            } else {
                logger.log("Device suspended. Unable to shut down");
            }
        } else {
            logger.log("Invalid handle for: " + DEV1.toString());
        }
    }
    ...
}


Agora com o gerenciamento do erro por meio de exceções:

public class DeviceController {
    ...
    public void sendShutDown() {
        try {
            tryToShutDown();
        } catch (DeviceShutDownError e) {
            logger.log(e);
        }
    }

    private void tryToShutDown() throws DeviceShutDownError {
        DeviceHandle handle = getHandle(DEV1);
        DeviceRecord record = retrieveDeviceRecord(handle);
        pauseDevice(handle);
        clearDeviceWorkQueue(handle);
        closeDevice(handle);
    }

    private DeviceHandle getHandle(DeviceID id) {
        ...
        throw new DeviceShutDownError(
            "Invalid handle for: " + id.toString()
        );
        ...
    }
    ...
}

Perceba como no segundo caso tudo ficou muito mais claro. Os algorítimos para o desligamento do dispositivo e a manipulação do erro ficaram separados.

Não retorne Null


Se você cair na tentação de retornar null, pare e pense se não seria melhor lançar uma exceção (thrown exception) ou então retornar um objeto do tipo Special Case.

O problema de trabalhar com funções que retornam null é que você tem que lembrar de lidar com esse retorno toda vez que ele for possível, o que leva à duplicação de código e torna sua programação propensa a erros, pois em diversos momentos você terminará esquecendo de lidar com o null como valor de retorno.

Se você estiver trabalhando com uma API que possui retorno null, o melhor é encapsular esses métodos com outros métodos que lancem exceções ou, ainda, que retornem objetos de casos especiais (Special Case Objects).

Não passe Null


Da mesma forma que não é bom retornar null, não é boa ideia passar null como argumentos de funções. Essa prática em geral está associada com funções que possuem diversos argumentos e eles devem ser fornecidos na ordem em que foram declarados. Assim, para pular um ou algum dos argumentos, passa-se o null. A prática é ruim porque obriga a que função seja escrita de forma a verificar e testar argumento por argumento. Não é eficiente e propicia esquecimentos e erros. O melhor é seguir a regra de que as funções devem ter nenhum, um, ou, quando muito, dois argumentos, de forma que não seja necessário "pular" argumentos declarados nem ficar verificando argumentos e o tipo deles.

Classes


Classes devem ser pequenas!


Tal como afirmado em relação às funções, as classes também devem ser pequenas. Porém, não se deve medir o tamanho de uma classe em linhas, mas sim em responsabilidades.


O primeiro indicativo é o nome da Classe. Se não conseguir encontrar um nome conciso para a classe, que expresse sua responsabilidade, é porque provavelmente a classe é grande demais. Quanto mais ambíguo o nome da classe, maior a chance de ela apresentar muitas responsabilidades. Por exemplo, classes que levam os nomes Processor ou Manager ou Super em geral apontam o indevido acúmulo de muitas responsabilidades.

O Princípio da Responsabilidade Única (Single Responsibility Principle)


O princípio da responsabilidade única estabelece que uma classe ou módulo deve ter uma, e apenas uma, razão para mudar. O princípio fornece tanto a definição de responsabilidade como um guia para o tamanho da classe. Classes devem ter apenas uma responsabilidade - uma razão para mudar.

Muitos programadores temem que um grande números de classes pequenas tornará o sistema difícil de ser entendido. No entanto, um sistema com diversas classes pequenas não tem mais partes mutáveis do que um sistema com poucas porém enormes classes. O sistema deve ser composto, portanto, de diversas classes pequenas. Cada classe deve ter apenas uma responsabilidade encapsulada, ou seja, deve possuir apenas uma razão para ser alterada, e deve colaborar com algumas outras classes para que o sistema alcance a finalidade desejada.

Coesão


Classes devem ter número pequeno de variáveis (instance variables). Cada um dos métodos de uma classe deve manipular uma ou algumas dessas variáveis. Em geral, quanto mais variáveis um método manipular, mais coeso esse método será em relação à classe. Porém, nem sempre será possível ou mesmo aconselhável que uma classe seja criada com tal nível de coesão. Mas, de qualquer forma, é desejável que o nível de coesão seja alto.

Atenção: a estratégia de manter as funções pequenas e a lista de parâmetros pequena ou até inexistente pode levar a uma proliferação de variáveis da classe que podem terminar sendo usadas apenas por um subgrupo de métodos da classe. Quando isso acontecer, provavelmente significa que pelo menos uma nova classe está tentando sair da classe maior. Você deve tentar separar as variáveis e métodos em duas ou mais classes, de modo que a nova classe seja mais coesa.

Manter coesão resulta em diversas classes pequenas


O mero ato de quebrar funções grandes em funções menores, por si só, já leva a uma proliferação de classes. Imagine uma função extensa com diversas variáveis declaradas dentro da função. Digamos que você queira extrair uma pequena parte dessa função para uma função separada. No entanto, o código que você deseja separar usa quatro das variáveis declaradas na função. Vem a pergunta: você deverá passar essas variáveis para a função extraída por meio de argumentos?

Com certeza não!

Se essas quatro variáveis forem promovidas a variáveis da classe (instance variables), você poderá extrair o código sem ter que passar nenhuma variável como argumento.

No entanto, por vezes isso pode ocasionar um acúmulo de mais e mais variáveis da classe que existem somente para permitir que algumas funções as compartilhem, e não que sejam necessariamente usadas pela maioria das funções da classe. Conforme acima exposto, esse acúmulo de variáveis não usadas pela maioria dos métodos da classe significa falta de coesão. Mas pare e pense: se há algumas funções que precisam compartilhar determinadas variáveis, então isso significa que tais funções e tais variáveis já merecem mesmo uma classe somente para elas. É o princípo da coesão em ação.

Logo, se uma classe começar a perder a coesão em razão da separação das funções em funções mais curtas, separe a própria classe em tantas classes quanto forem necessárias para que cada uma delas mantenha sua própria coesão.

Organizando para a Alteração


Em todos os sistemas as mudanças podem ser contínuas. Só que toda mudança causa o risco de o restante do código não funcionar mais. O programador então, resite em fazer alterações. Por isso, em um sistema limpo (ou "clean"), as classes devem ser organizadas de forma a reduzir os riscos decorrentes de mudanças.

A classe Sql a seguir é usada para gerar comandos SQL apropriados a partir de metadados que forem fornecidos. Para os fins do exemplo, ainda não se trata de classe completa. Falta-lhe, por exemplo, o suporte para comandos do tipo update. Quando chegar a hora de implementar tais métodos, teremos que abrir a classe para as mudanças. O problema é que ao abrir a classe introduzimos o risco próprio da mudança, qual seja, quebrar outras partes do código.

public class Sql {
    public Sql(String table, Column[] columns)
    public String create()
    public String insert(Object[] fields)
    public String selectAll()
    public String findByKey(String keyColumn, String keyValue)
    public String select(Column column, String pattern)
    public String select(Criteria criteria)
    public String preparedInsert()
    private String columnList(Column[] columns)
    private String valuesList(
        Object[] fields, final Column[] columns
    )
    private String selectWithCriteria(String criteria)
    private String placeholderList(Column[] columns)
}

A classe Sql acima deverá sofrer alterações sempre que adicionarmos um novo tipo de comando. Deverá mudar também quando alterarmos os detalhes de um único tipo de comando, como por exemplo se precisarmos alterar a funcionalidade select para suportar subselects. Essas duas razões para mudar indicam que a classe Sql viola o princípio da responsabilidade única (single responsibility principle).

Essa violação pode ser facilmente percebida até mesmo pela forma como a classe foi organizada. Existem métodos privados, como por exemplo o selectWithCriteria, que visivelmente somente se relaciona com os comandos do tipo select. Métodos privados que se relacionam somente com pequeno subgrupo de funções da classe são bons indicativos da necessidade de melhoria no design dessa classe.

Se não houver a menor necessidade de atualizar a classe (ou "abrir a classe" para mudança), então em princípipo ela poderia ser deixada como está. Mas a partir do momento que você se pegar "abrindo a classe" para mudança, deverá considerar a possibilidade de melhorar o design dela.

Imagine a solução a seguir ilustrada. Nela, cada uma das interfaces públicas dos métodos definidos na classe anterior passou a integrar cada uma sua própria classe derivada da classe Sql. E os métodos privados, tais como valuesList, são movidos diretamente para onde são necessários. E o comportamento privado comum foi isolado em um par de classes utilitárias, Where e ColumnList.

abstract public class Sql {
    public Sql(String table, Column[] columns)
    abstract public String generate();
}

public class CreateSql extends Sql {
    public CreateSql(String table, Column[] columns)
    @Override public String generate()
}

public class SelectSql extends Sql {
    public SelectSql(String table, Column[] columns)
    @Override public String generate()
}

public class InsertSql extends Sql {
    public InsertSql(String table, Column[] columns, Object[] fields)
    @Override public String generate()
    private String valuesList(
        Object[] fields, final Column[] columns
    )
}

public class SelectWithCriteriaSql extends Sql {
    public SelectWithCriteriaSql(
        String table, Column[] columns, Criteria criteria
    )
    @Override public String generate()
}

public class SelectWithMatchSql extends Sql {
    public SelectWithMatchSql(
        String table, Column[] columns, Column column, String pattern
    )
    @Override public String generate()
}

public class FindByKeySql extends Sql {
    public FindByKeySql(
        String table, Column[] columns,
        String keyColumn, String keyValue
    )
    @Override public String generate()
}

public class PreparedInsertSql extends Sql {
    public PreparedInsertSql(String table, Column[] columns)
    @Override public String generate() {
        private String placeholderList(Column[] columns)
    }
}

public class Where {
    public Where(String criteria)
    public String generate()
}

public class ColumnList {
    public ColumnList(Column[] columns)
    public String generate()
}

Assim, o código em cada classe torna-se extremamente simples. O risco de uma função quebrar outra ficou reduzido a praticamente zero. Além disso, o isolamento de cada classe tornou a produção de testes tarefa fácil (Test Driven Development).

E quando chegar a hora de adicionar o comando update, nenhuma das classes existentes precisará ser alterada. A lógica para criar comandos update será escrita em nova subclasse da classe Sql chamada UpdateSql. Nenhum outro código no sistema será quebrado por conta desse acréscimo.

Assim, a lógica reestruturada representa o melhor dos mundos. Suporta o princípio da responsabilidade única e também suporta outro fator chave do desenvolvimento orientado por objetos, que é o "Princípio Aberto-fechado" (Open-Closed Principle). Classes devem estar abertas a extensões mas fechadas para modificações. A classe Sql reestruturada está aberta para permitir nova funcionalidade por meio de subclasses, mas isso pode ser feito mantendo-se as demais classes fechadas. Simplesmente colocaremos a nova classe UpdateSql no lugar dela.

Em um sistema ideal, incorporamos novas funcionalidades estendendo o sistema e não fazendo modificações no código existente.

Isolando das Mudanças


Classes concretas implementam detalhes (código) e classes abstratas representam apenas conceitos. Uma classe cliente que dependa de detalhes concretos está em perigo quando esses detalhes mudarem. Por isso, interfaces e classes abstratas podem ser introduzidas para ajudar a ilosar o impacto desses detalhes.

A dependência de detalhes concretos torna complicados os testes do sistema. Imagine a classe Portfolio que dependa da API externa TokyoStockExchange para obter o valor do portfolio. Testar a classe vai ser extremamente difícil por conta da mutabilidade do valor retornado. A cada cinco minutos a resposta será diferente.

Em vez de definir a classe Portfolio de forma que ela dependa diretamente de TokyoStockExchange, podemos criar uma interface, SotckExchange, que declara um único método:

public interface StockExchange {
    Money currentPrice(String symbol);
}

Então definimos TokyoStockExchange como implementação dessa interface. Também nos certificamos de que o construtor de Portfolio receba como argumento uma referência do tipo StockExchange:

public Portfolio {
    private StockExchange exchange;
    public Portfolio(StockExchange exchange) {
        this.exchange = exchange;
    }
    // ...
}

Agora poderá ser criada uma implementação passível de teste da interface SotckExchange que emula a TokyoStockExchange. Essa implementação de teste irá ajustar o valor atual para qualquer símbolo (variável symbol no exemplo) que usemos no teste. Se nosso teste demonstrar a compra de cinco ações da empresa imaginária MSFT para o nosso portfolio, criamos a implementação do teste para sempre retornar $100 por ação dessa empresa. Nossa implementação teste da interface StockExchange fica reduzida a uma simples busca de tabela. Podemos então escrever um teste que espera o total de $500 para nosso valor do portfolio quando são adicionadas 5 ações.

public class PortfolioTest {
    private FixedStockExchangeStub exchange;
    private Portfolio portfolio;

    @Before
    protected void setUp() throws Exception {
        exchange = new FixedStockExchangeStub();
        exchange.fix("MSFT", 100);
        portfolio = new Portfolio(exchange);
    }

    @Test
    public void GivenFiveMSFTTotalShouldBe500() throws Exception {
        portfolio.add(5, "MSFT");
        Assert.assertEquals(500, portfolio.value());
    }
}
Se um sistema for separado o suficiente para ser testado dessa forma, será também mais flexível para promover o reuso de código. Assim, os elementos ficam isolados um dos outros e isolados de mudanças. Essa classe então atenderá a outro princípio de elaboração de classes conhecido como Inversão de Dependência (Dependency Inversion Principle - DIP). Em resumo, esse princípio estabelece que as classes devem depender de abstrações e não de detalhes concretos.

Em vez de depender da implementação de detalhes da classe TokyoStockExchange, nossa classe Portfolio agora depende da interface StockExchange. Essa interface representa o conceito abstrato de perguntar qual é o preço corrente de um símbolo ('symbol' no exemplo). Essa abstração isola todos os detalhes específicos para se obter tal preço, incluindo de onde e de que forma esse preço pode ser obtido.

Há muito mais


Nem de longe o resumo contido nesses posts esgota o tema das boas práticas em programação.

Em posts futuros procurarei resumir mais conceitos e práticas interessantes.

De qualquer forma, há muito material disponível para pesquisa e sem dúvida o programador profissional deve se aprimorar constantemente para escrever o desejado "clean code".