quinta-feira, 6 de agosto de 2015

O absurdo da notação húngara nos dias atuais

Define-se por notação húngara a prática de prefixar variáveis com letras que indiquem o seu tipo. Por exemplo i para inteiros, d para datas, f para floats. 

     As variáveis data de nascimento e nome em notação húngara seriam ddatanascimento e snome, no pior dos casos, ou dDataDeNascimento e sNome se o "programador" resolveu usar camelCase. 

     Como mencionei nos meus artigos anteriores, e como mencionado nos livros "Código Limpo" e qualquer livro sobre XP, essa prática caiu em desuso. 

     Ela foi útil um dia para identificar o tipo das variáveis, e de fato identificava. Hoje damos nomes significativos para as variáveis e deixamos que sua declaração e as facilidades da IDE identifiquem o seu tipo. Além disso hoje podemos mudar o tipo de uma variável mantendo o seu nome e o seu uso, coisa que no passado geraria alguma inconsistência semântica na hora de ler e dar manutenção no código. 

     Soma-se a isso o fato de no passado não existirem, nas linguagens comumente usadas, tipos de dados dinâmicos (variants se aproximavam disso) ou definidos em tempo de execução, tampouco tipos anônimos. 

     Num contexto orientado a objetos o uso de notação húngara, além de ser um overhead, é ridículo e chega a ser prejudicial. 

     Já vi, em várias empresas, classes em C# ou Java prefixadas com "cls". Qual o propósito disso? A notação húngara foi criada para se prefixar variáveis, e nunca tipos ou records. Uma classe é um tipo definido pelo programador, um tipo estruturado, uma classe é um tipo, portanto nunca deveria ser prefixado com a abreviação de um tipo. 

     Qual o propósito de prefixar as classes com cls se TODAS as classes sem exceção forem prefixadas com cls? No que cls as distingue das outras? 

     Pense na diferença entre dizer que todos os animais são do tipo animal, e todos os tipos são do tipo tipo. Redundância. Sempre é prejudicial. 

     Prefixar classes com cls não as distinguem umas das outras, mas o pior é prefixar objetos dessas classes, que podem ser inúmeras e com inúmeros significados, com a letra "o", só porque são objetos.   É afirmar erroneamente e categoricamente que os objetos oUsuario e oProduto são do mesmo tipo, quando suas classes sequer tem algum grau de parentesco. 

     Usar notação húngara em programas modernos e linguagens orientadas a objetos é sinal de acomodação profissional, falta de atualização, porquice ou até mesmo estupidez. 

     Linguagens como Object Pascal e as IDE's Delphi e Lazarus te obrigam a usar notação húngara na criação de classes por um motivo "legado": o código nestas linguagens tem dois níveis de encapsulamento, a unit e a classe/type. Isso faz com que a própria unit seja um "tipo" e seu nome faz parte do namespace total das classes que estão dentro dela. Como o pascal permite variáveis (públicas ou privadas) estáticas fora das classes (globais), não é permitido que a unit tenha o mesmo nome de sua classe, pois isso geraria dois "tipos" com o mesmo nome. 

     Veja que é uma limitação do Delphi / Lazarus: mesmo que você obedeça a orientação e boa prática de ter uma única classe por unit/arquivo, uma classe não pode ter o mesmo nome do arquivo físico onde está contida. E o arquivo físico deve ter o mesmo nome que seu nome "lógico" de unidade. 

     Em contraste com isso linguagens como C# e Java permitem ter classes com o mesmo nome que o arquivo onde elas estão contidas, e isso não te impede de criar outras classes com outros nomes dentro do mesmo arquivo embora isso não seja uma boa prática. 

     Embora o uso de notação húngara seja  justificado no caso do Delphi e do Lazarus, eu acredito que prefixar ou sufixar os arquivos com a letra u (de unit) é melhor do que prefixar os tipos. Assim você tem os tipos de dados, as suas classes, os seus objetos de negócio ( o seu domínio ) com nomes significativos, sem problemas de semântica e com um dicionário padrão do domínio. Isso traria um padrão de nomenclatura mais moderno para quem programa nessas linguagens. Mas nem tudo são flores...

     Por padrão das units de sistema e dos frameworks que acompanham as ferramentas todas as classes (e tipos, records etc) são prefixadas com a letra T (de type) . Então todas as classes de sistema já existem e são prefixadas com T. Não faz sentido você criar uma classe Produto, mas na hora de colocar vários produtos numa lista precisar de uma TList<Produto>. O uso de prefixo de um lado e não do outro pode gerar um gap semântico. 

     Além disso tem o problema das interfaces. As modernas técnicas de POO e DDD e os princípios  SOLID nos ensinam a programar orientado a interfaces, e grandes nomes do projeto e desenvolvimento de software, como Kent beck, Robert Martin, Martin Fowler afirmam categoricamente que: 
  1. Você deve usar as modernas técnicas de POO, padrões de projeto e DDD e 
  2. Você deve usar padrões de nomenclatura, codificação e formatação para nivelar a sua equipe com um mesmo estilo de codificação e todos progredirem rápido. 
     O problema disso é que os melhores padrões de nomenclatura encontrados na literatura hoje afirmam que as interfaces devem ser prefixadas com I .... olha a notação húngara aí geeeeeente!. 

     Acredito que não dá para ser dogmático a ponto de não prefixar as interfaces com I. Se você ver uma interface chamada "Pessoa" é meio difícil saber que é uma interface sem olhar na sua declaração, seus métodos "pelados" e falta de implementação. Ainda assim ela pode ser confundida com uma classe abstrata. Até na UML existem estereótipos e símbolos diferentes para definir as interfaces. 

     Mas, se você não estiver escrevendo uma interface e nem usando Object Pascal, Pelo amor de Deus, não usem mais notação húngara.

sexta-feira, 20 de março de 2015

Modernos padrões de nomenclatura de codificação

     Os padrões apresentados aqui são fruto de uma pesquisa em 2 livros e 4 sites, experiências profissionais do autor, consenso do mercado e recomendações de práticas da Microsoft. Como qualquer convenção ou tecnologia, este não é 100% correto, não é uma verdade absoluta e não é uma bala de prata. São apenas guidelines para que todos nós escrevamos bom código, código limpo e livre de bad smells e que possamos entender e melhorar os códigos uns dos outros, evoluindo como equipe. Nesse texto eu quis expressar não somente regras de nomenclatura e codificação, mas também apresentar algumas boas práticas para um código mais coeso e flexível.
     Esses são os padrões que adotamos na empresa onde trabalho e não há nenhum motivo especial para você e sua equipe adotarem este. Os padrões de codificação devem ser um consenso da equipe.
     Estes foram elaborados com ajuda de outros padrões prontos de outras empresas, unindo o que eu achei de melhor nos padrões da Microsoft e nas práticas já consolidadas da comunidade.

   

"É impossível para um homem aprender aquilo que ele acha que já sabe."                                                                                                                     - Epíteto



     Em se definindo e aplicando “Padrões de Codificação e Documentação”, há um enorme ganho com facilidade de implementação e melhorias no código em:

  • Visibilidade;
  • Portabilidade;
  • Reaproveitamento;
  • Redução de tempo em:
  • Adaptação de novos desenvolvedores.
  • Manutenção;
  • Migração;
  • Identificação, rastreamento e correção de erros.
  • Diminuição de reescrita de código por falta de documentação.


Fonte: http://blog.walkeralencar.com/archives/233


     Coding standards tem sido alvo de várias disciplinas, e tratado em vários livros de programação e boas práticas. Alguns livros que abordam o tema são livros de XP e o Código Limpo, ambos abordando outros aspectos do bom código e da disciplina dos programadores além do aspecto dos padrões de codificação.

     A própria microsoft já fornece, em seu principal veículo de comunicação, msdn, os padrões de codificação. Isso garante, por exemplo, que qualquer programador recém-contratado do mercado já esteja hasbituado com os padrões de codificação.
https://msdn.microsoft.com/en-us/library/ff926074.aspx
Recomendo a adoção deste padrão com algumas alterações muito comuns na comunidade.

   

Nossas Regras

     A regra geral é seguir as convenções e práticas do próprio .net framework (que foram adotadas como padrão pelo mercado) e manter a consistência.


     Definições de casing conventions:

          PascalCase/UpperCamelCase  - Consiste em começar com maiúscula e usar maiúscula na inicial de cada palavra composta. Exemplo: FuncionarioMensalista
Geralmente é utilizada para nomear propriedades e campos públicas e métodos (públicos ou privados). Também deve ser usado para nomear constantes.

          camelCase - Consiste em se iniciar com minúscula e usar maiúscula para a primeira letra de cada palavra composta. Exemplo: funcionarioMensalista
          Geralmente são usados em nomes de variáveis locais e propriedades privadas.


     Notação Húngara

          Os padrões de nomenclatura da comunidade C# em geral dizem para não confiar o tipo de uma variável a seu nome:
"Do not rely on the variable name to specify the type of the variable. It might not be correct."
           Isso põe por terra todo o conceito antiquado de notação húngara.  ( http://pt.wikipedia.org/wiki/Nota%C3%A7%C3%A3o_h%C3%BAngara )
          Outras coisas que põe por terra a notação húngara são as boas práticas de se criar nomes significativos para variáveis e métodos (XP, Código Limpo), que expressem ação e intenção e que dispensam comentários.
          Outra crescente desvantagem da notação húngara é que com os frameworks e ides cada vez mais completos um número cada vez maior de classes prontas, componentes e widgets vêm empacotados com os produtos que usamos (visual studio e .net framework) fora os adquiridos de terceiros e os open-source.
          Criar uma abreviação de duas ou três letras para cada tipo destes torna-se um verdadeiro inferno.
          Notação húngara já é um conceito bem antigo e com certeza seu uso traz muitas desvantagens. Tipos dinâmicos e definição de tipo em runtime perdem todo o sentido com notação húngara, e não é necessário visto que a IDE pode vasculhar e te mostrar todos os atributos de uma variável.
          Quando você cria suas classes você cria seus tipos de dados com lógica própria. E frequentemente renomeamos nossas classes, mudamos, extraímos uma da outra, juntamos, derivamos. Fica impossível arrumar uma abreviação consistente para todas elas.


     Noções gerais ausentes no padrão da Microsoft

           Embora os padrões da microsoft abordem o tema das chaves, arrays, linhas e espaços em branco (assuntos ausentes nos nossos padrões), ela não aborda os padrões de nomenclatura (definidos como notação húngara nos nossos padrões mais antigos).
           É uma prática comum nas linguagens derivadas de c, ao se nomear campos privados dos quais dependem propriedades (públicas ou privadas), nomear em lowerCamelCase precedido de _.
           Outra prática comum é nomear constantes todas em MAIUSCULAS. Isso torna claro que o identificador é somente leitura, além de tornar claro seu tipo e que está relacionado à uma regra de negócio e não à arquitetura do software ou da informação, porém existem recomendações no mercado para não usá-las, portanto seguiremos as recomendações e não usaremos. Nomes de constantes em maiúsculas atraem muito a atenção do programador, distraindo-o, além de não ser consistente com o .net framework.
           Não se deve usar _ entre as palavras de um método, priopriedade ou variáveis, no entanto isso pode ser tolerado em uma constante.
          Um nome deve identificar um elemento (quem ele é) e não generalizá-lo. Além disso deve indicar o que o elemento faz e para que serve, não o que ele é.


     Metáfora

          Todo sistema deve ter uma metáfora que o defina em poucas palavras. Isso ajuda a equipe a formar um vocabulário comum do domínio da aplicação (ou das aplicações) e falarem a mesma língua. Também ajuda na escolha de bons nomes para os projetos e seus componentes. Bons nomes são essenciais para um software coeso e auto-documentável. Bons nomes dispensam comentários ou documentações complexas. Se for gastar um tempo desenvolvendo um software, comece  gastando um bom tempo para o seu nome. O mesmo vale para uma classe ou método: tome tempo para escolher um bom nome. Isso te ajudará a manter o princípio da responsabilidade única  e o princípio da segregação de interface.
          Use nomes significativos e pronunciáveis. Use nomes que expressem propósito.
          Sistemas amplamente usados são baseados em metáforas, por exemplo o sistema de arquivos do Windows é baseado na metáfora do Arquivo (armários para se guardar fichas / pastas).
          Essas ideias estão em acordo com os princípios do XP e do DDD.


     Solution          

          A solution pode agrupar uma série de projetos / programas que são parte de um mesmo sistema, por isso dê um nome para a solution condizente com a metáfora do sistema. A ideia expressa pela solution deveria ser compartilhada pelos seus projetos.
          Exemplo: ServiceDesk
   

     Projetos

          Use a solution como base ou prefixo para o nome dos projetos, e coloque seu objetivo e/ou ambiente como sufixo. Se o seu projeto for dividido em camadas físicas, como Core, DAL/Data, Domain, GUI use esses nomes para identificar a camada a que pertence o projeto.
          Exemplo: ServiceDesk.Web


     Arquivos

          Salvo raras exceções, cada arquivo.cs deve ter apenas uma classe, e o nome do arquivo deve ser o nome desta classe. Uma exceção são os arquivos de partial classes, esses devem ter um sufixo que identifiquem o propósito do arquivo ou o porque dele estar separado. Por exemplo designer ou generated
          Exmeplos: Default.cs Default.design.cs
          Porque: isso é consistente com as práticas da Microsoft no source do próprio .net. Arquivos são ordenados alfabeticamente pelo SO, e isso ajuda arquivos de classes parciais a permanecerem adjacentes.


     Namespaces

          Use PascalCase para nomear os namespaces. Use uma estrutura clara e bem definida.
          Exemplo: NomeDaEmpresa.NomeDoProjeto.NomeDaCamada.NomeDoModulo.NomeDoComponente
                         BBI.ServiceDesk.Web.Data.Services.Repository
          Porque: é consistente com as práticas da Microsoft e mantém uma boa organização da sua base de código.


     Classes

          Use PascalCase para nomear classes. Use substantivos ou predicados nominais em caso de palavras compostas. Não use verbos.
          Exemplo: Funcionario, GrupoDeFuncionario, ProdutoComposto
          Porque: classes são tipos de objetos, mini programas, devem expressar um conceito e não uma ação. Um conceito é melhor expresso por substantivos e adjetivos do que por verbos. Isso é consistente com as práticas da Microsoft e mais fácil de ler.


     Interfaces

          Use PascalCase para nomear interfaces, mas prefixe as mesmas com I. Isso ajuda a diferenciar interfaces de classes abstratas ou outros tipos de dados, ajuda a identificar o que as classes que as implementam fazem, e é uma prática comum do mercado desde a época do COM/COM+.
          Use substantivos, predicados nominais ou adjetivos/advérbios para nomear interfaces.
          Exemplos: IEnumerable, IList, IHierarchyData, IEntity
          Porque: Isso é consistente com os padrões da Microsoft e do COM/COM+, além disso interfaces podem ser usadas para decorar classes, como flags, ou usadas como atributos. Por issp interfaces podem ser nomeadas com adjetivos ou advérbios, pois elas modificam ou segregam comportamento de outras classes.
          Interfaces são a base da abstração e da segregação de responsabilidade. São contratos entre duas partes, ou pontos de encaixe entre componentes. Programe orientado a interfaces e não à implementação.


   

     Ordem dos elementos

          Declare todas as variáveis membro no topo da classe, com as estáticas acima de todas. Utilize a ordem:
          Membros/Fileds


  1.   privadas estáticas
  2.   públicas estáticas (evite-os a todo custo)
  3.   privadas de instância
  4.   públicas de instância (evite-os, transforme-os em propriedades sempre que possível)
  5.   propriedades protegidas
  6.   propriedades públicas de instância



         Métodos (na ordem do mais genérico/abstrato para o mais específico, dos métodos que chamam outros para os chamados)


  1.  Construtores e Destrutores 
  2.  Factory methods públicos de classes (são construtores especializados)
  3.  Métodos públicos de classe
  4.  Métodos privados de classe
  5.  Métodos públicos de instância
  6.  Métodos protegidos de instância
  7.  Métodos privados de instância


          Porque: é uma prática geralmente aceita, evite que se fique procurando por variáveis, garante que a leitura de um source seja como a leitura de um artigo de jornal: dados genéricos em cima, dados detalhados abaixo, com nível crescente de granularidade e decrescente de abstração de cima para baixo. Para isso os métodos que são chamados por outras classes ficam acima, e os chamados pela própria classe abaixo. Com isso você precisa ler apenas o topo da classe para saber o que ela faz e como ela funciona apenas visualizando sua interface implícita sem se aprofundar em sua implementação.


     Propriedades (privadas/protegidas/públicas)

          Use PascalCase para o nome de propriedades. Use preferencialmente substantivos e predicados nominais.
          Exemplo: Nome, DataNascimento
          Porque: além de facilitar a leitura, trata-se de objetos pertencentes a outro, como peças de algo maior, portanto todos substantivos. Está de acordo com as práticas da Microsoft para o .net
   

     Fields ou Variáveis de instância ou Member Variables


  •            Públicos : não devem existir
  •            Privados: aplicar camelCase precedido de _

          Exemplo: _dataNascimento 
          Porque: Está em acordo com as práticas comuns do mercado para linguagens derivadas de _, ajuda a separar os campos privados dos argumentos de métodos / constructors sem a necessidade do uso do this, isola essas variáveis da digitação acidental (porque obriga a digitação do _ no início, excluindo-as do intellisense / autocomplete).


     Variaveis Locais

          Use camelCase para os nomes de variáveis locais, use nomes significativos, evite prefixos e abreviações a não ser que sejam bem conhecidas, não use notação polonesa.
          Exemplo: enderecoServidor, connectionString
          Porque: além de ser fácil de ler é consistente com a metodologia da Microsoft


     Métodos

          Use PascalCase para nomear os métodos. Use o formato Verbo ou Verbo+Complemento para nomear os métodos. Se o verbo for intransitivo o método não deveria ter parâmetros.
          Exemplo:  Salvar(grupoDeFuncionario)
                         Excluir(tipoDeTarefa)
                         ListarEntreDatas(dataInicial, dataFinal)
          Porque: Consistente com o .net framework e fácil de ler. Verbos expressam ações, por isso são melhores que substantivos para expressar métodos. É natural se ler e escrever dessa forma



     Argumentos de Métodos

          Use camelCase para argumentos de métodos, use substantivos. Se o nome de um método contém um verbo, os argumentos devem ser complementos desse verbo.
          Exemplo: CalcularReajuste(porcentagemAumento)
                         Salvar(grupoDeFuncionario)
                         Excluir(tipoDeTarefa)
                         ListarEntreDatas(dataInicial, dataFinal)
          Porque: mais fácil de ler e entender. Tudo que pode ser verbalizado pode ser melhor armazenado pelo nosso cérebro. É condizente com as práticas da Microsoft no .net.


    Ainda Sobre Argumentos de Métodos

          Se você precisar de de mais de 3 argumentos no seu método e/ou precisar usar parâmetros nomeados há uma grande chance de você estar simplesmente enrolado e seu método precisar ser refatorado. Verifique se seu método não está com responsabilidades demais.
          Se você passa argumentos booleanos ou enums para um método onde ele tomará uma ação diferente dependendo do parâmetro é uma evidência clara de que você precisa decompor este em dois ou mais métodos. Dê uma olhada nos padrões de projeto GOF "Template Method" e "Strategy" que eles podem de dar uma ideia de como refazer estes métodos.
          Porque: Métodos grandes são difíceis de ler e de manter. Eles tendem a crescer mais a cada manutenção com a adição de desvios condicionais para fazerem coisas diferentes dependendo de configuração ou parâmetros. Esses métodos degradam o sistema rapidamente, ficando com código inatingível (nunca executado), código comentado e código com side effects (statefull) que não podem ser testados.


     Parâmetros-Tipo (type parameters)

          Type Parameters são os parâmetros que representam um tipo em uma expressão que usa generics. Por exemplo na definição de List<T>   T é o type parameter. Na definição de classes genéricas sempre use PascalCase precedido de T para nomear os type parameters.
          Exemplo: Repository<TEntity> : IRepository<TEntity> where TEntity : Entity

    Strings

          Concatene strings pequenas com sinal de " + " porém use StringBuilder para strings grandes. Use string.Format para strings pré formatadas e substituição de placeholders.
          Porque: Embora tenha sobrecarga de operadores nativa para parecer um value type pimitivo as strings são objetos (reference types). Isso significa que a cada atribuição ou concatenação de duas strings, dois objetos são destruídos  e um novo é criado. Isso cria um grande overhead, principalmente dentro de loops. StringBuilder é uma classe que concatena várias strings de uma só vez sem precisar destruir e recriar a cada adição fazendo um uso mais inteligente da memória. Foi feito para ser usado em loops onde muitas strings são concatenadas, como uma saída html por exemplo.



    Enums

          Use PascalCase. Use substantivos no singular se for um Enum comum. Se for um Enum do tipo bit field / flags (a serem combinados múltiplos valores com o operador "|") pode-se usar substantivos no plural, pois sugere que mais de um valor pode ser usado.
          Exemplo:
enum Importance
{
None,
Trivial,
Regular,
Important,
Critical
}; 

          Porque: Consistente com o .net framework e mais natural para ler


     if ... else if ... else

               Sempre coloque um bloco de código com { } em estruturas de decisão if pulando uma linha antes de { e outra ante de }. Mesmo com if's que só terão uma linha de código.
               Se você precisar de mais de 3 níveis de aninhamento para uma estrutura if ou case significa que você está enrolado. Provavelmente seu método faz coisas demais ou sua classe tem muitas responsabilidades e muitos motivos pra mudar.  
            Porque: if (b1) if (b2) Foo(); else Bar(); // consegue dizer ao primeiro golpe de vista a qual if pertence o else?      


     Case ... default

               Sempre tenha um label default em uma estrutura case. Mesmo se ele nunca devesse ser atingido. Coloque nele código para disparar uma exceção caso um parâmetro inválido tenha sido erroneamente enviado para o case analizar.



     Loops

               Se você precisar de dois níveis de loops aninhados considere transformar o mais interno em um método privado. Se você estiver trabalhando com mais 3 níveis verifique se o método e/ou a classe não estão com responsabilidades demais e se não estão intestáveis.
               Jamais altere a variável de controle do loop for ou foreach dentro do loop. Isso pode levar a loops infinitos.



     Alinhamento vertical de chaves

          Alinhe as  chaves verticalmente. Não mantenha a chave no final da linha, quebre a linha antes da chave e mantenha elas alinhadas. Sempre abra e feche as chaves em novas linhas.
          Certo:
         public virtual void setMensagemExplicacao( string mensagem)
        {
            MensagemExplicacao = mensagem;
            lgdExplicacao.Visible = true;
            lgdExplicacao.InnerText = mensagem;
        }

          Errado:
         public virtual void setMensagemExplicacao( string mensagem){
            MensagemExplicacao = mensagem;
            lgdExplicacao.Visible = true;
            lgdExplicacao.InnerText = mensagem;
        }

         public virtual void setMensagemExplicacao( string mensagem){
            MensagemExplicacao = mensagem;
            lgdExplicacao.Visible = true ;
            lgdExplicacao.InnerText = mensagem;}

          Porque:   Chaves abertas ou fechadas no final de uma linha de código passam despercebidas e não são legíveis. O código pode ser confundido como parte do código acima.


     Espaços em branco

          Deixe espaços em branco entre os operadores e seus operandos. Isso facilita a leitura.    
          Deixe espaços em branco entre um if, for ou foreach e sua expressão. Exemplo: if (UmFuncionario.Equals(EsteFuncionario))
       

     Quebra de linhas

          Quebre linhas antes de abertura e fechamento de chaves, quebre linhas para separar ideias, agrupar conceitos relacionados, separar conceitos não relacionados, separar a declaração de variáveis de seu uso, quebrar cadeias longas de caracteres ou encadeamentos longos de chamadas de métodos ou propriedades.
          Também é bom quebrar as linhas em chamadas de funções com listas de parâmetros muito longas, e edentar esses parâmetros.
          Quebre linhas antes e depois de regions, antes e depois de um using, e separando os fornecedores na lista de usings.

     Regions

          Use regions para agrupar conceitos e separar interface de implementação, eventos de métodos e de variáveris.
          Não use regions aninhadas pois isso impõe complexidade desnecessária.
          Não use regions para esconder código comentado, sujeira e gambiarras.

     Indentação/Recuo

          Use a indentação padrão do C# no visual studio (um tab = 4 espaços). Indente conceitos subordinados. Se precisar separar cadeias longas de chamadas em linhas, ou cadeias longas de parâmetros quebradas em linhas. Quebre as linhas de uma sentença SQL e Intente o SQL.
          Exemplo:
            ChamadaDeFuncao(
                 par1,
                 par2,
                 par3,
                 par4,
                 par5,
                 par6);
            {
                 //código
            }
                               Modulo obj = session
                            .CreateCriteria< Modulo>()
                            .SetTimeout(120)
                            .SetLockMode( LockMode.None)
                            .SetCacheable( true)
                            .Add( Restrictions.Eq("Nome" , nome))
                            .UniqueResult< Modulo>();
     

     Comentarios

           Algumas definições dos nossos padrões foram feitas para VB antigo e se chocam diretamente com os padrões da microsoft, por exemplo o padrão de comentários:
"Do not create formatted blocks of asterisks around comments."
          Há comentários que prejudicam a legibilidade do código, não são atualizados no mesmo ritmo do código, sujam o mesmo e poderiam facilmente ser substituídos por nomes significativos
          Há comentários que simplesmente perdem o sentido pouco tempo depois de terem sido colocados, simplesmente porque o código logo abaixo deles ou o objetivo do sistema em si foi mudado.
          Não comente o código simplesmente para desativá-lo esperando que um dia vá precisar dele. Antes de dar check-in, delete o código comentado. Código comentado apenas emporcalha o sistema, deteriorando-o mais rápido. Outros programadores acharão que é importante e evitarão removê-lo. Se um dia, quando ele não for mais útil, ele acidentalmente for descomentado ele pode comprometer todo o funcionamento do sistema. Confie no seu sistema de versionamento.
          Não coloque cabeçalhos com informações de autor, versionamento, changelog e bugtracking em comentários. Confie no seu sistema de versionamento, faça os comentários de changelog ao dar check-in no sistema, controle o bugtracking com um sistema apropriado. Uma exceção à regra é quando há motivos legais para assegurar o  autor do código, então pode existir um cabeçalho com o autor.
          Não comente o óbvio, coisas muito simples ou muito bem  estabelecidas. Se um comentário óbvio se faz necessário, então na verdade é necessário renomear seu método para um nome que expresse seu propósito.
          Quando necessário, comente em uma linha separada (geralmente acima) e não na linha do código
          Comece o comentário com letra maiúscula e termine com um ponto
          Insira um espaço entre o // e o início do comentário


     Comentários de documentação

          Comentários de documentação são precedidos por /// e servem para definir a estrutura e uso de classes, métodos e propriedades para geração de documentação automática com ferramentas como Doxygene, NDoc e SandCastle.
          Como seu objetivo é documentar uma interface implícita ou API para uso de outros programadores, comente apenas métodos e propriedades públicos e nunca comente membros privados.
          Porque: Isso está de acordo com o Princípio SOLID de programar voltado a interfaces e não implementação. Você não quer que outros programadores usem os métodos privados, então não há necessidade deles aparecerem na documentação. Assim você encapsula e protege o source em um nível adicional e está livre para mudar a implementação contanto que mantenha a mesma interface e funcionalidade.



     Try ... Finally

          Use o using sempre que seu bloco try ... finally servir apenas para chamar dispose em um recurso não gerenciado.
          Exemplo:
// This try-finally statement only calls Dispose in the finally block.
Font font1 = new Font("Arial", 10.0f);
try
{
    byte charset = font1.GdiCharSet;
}
finally
{
    if (font1 != null)
    {
        ((IDisposable)font1).Dispose();
    }
}
// You can do the same thing with a using statement. 
using (Font font2 = new Font("Arial", 10.0f))
{
    byte charset = font2.GdiCharSet;
}




     Refactoring

          Refactorings são saudáveis e devem ser feitos. Sempre que achar que um código pode ser melhorado quanto à legibilidade melhore-o. Sempre que algo pode ser feito de maneira mais rápida, mais fácil, mais elegante ou mais correta, refaça. Sempre que algo soar estranho, parecer "cheirar", troque. Use a regra do escoteiro e deixe o código mais limpo do que quando você encontrou.


     Gambiarras

          Toda gambiarra no código se volta contra você uma hora ou outra. Gambiarras causam infernos de manutenção, horas extras e expedientes de fim de semana.
          Toda gambiarra clama em alta voz por um refactoring. Um código ruim pode ter bad smells que são fruto de desconhecimento, limitações tecnológicas, cansaço ou erro humano, MAS gambiarras são sinais de relaxo, falta de ética e desorganização pessoal. O prazo não é desculpa para se fazer uma gambiarra. Se há uma forma mais elegante de se escrever um código procure melhorá-lo, mas se há uma forma definitivamente correta de se escrever um código, então use essa forma.


     Excessões à regra     

          Widgets demasiadamente usados como Button e Textbox tem padrões de nomenclatura derivados da notação húngara fortemente entrincheiradas em nosso dia - a - dia, por isso são aceitáveis coisas como botões precedidos de btn (btnSalvar) e inputs precedidos de txt (txtNome). Isso está em conformidade com as práticas da Microsoft, pois os widgets de tela do office são todos prefixados.    
          Variáveis de contadores de laço/iteradores podem ter um nome de uma letra só, como i,j,k,l,c. É mais inteligível desta forma, uma vez que os caracteriza como iteradores, além disso eles têm um escopo muito local, muito limitado.  
          Em linguagens onde o nome da classe não pode ser igual ao nome do arquivo que a contém é comum, em PascalCase, prefixar as classes com T, mas apenas nesses casos.  
          Em linguagens que não são case sensitive, como Delphi/Pascal/SQL, não é possível declarar um campo privado com camelCase e a propriedade que o acessa com o mesmo nome em PascalCase como abaixo:
 
//Nào faça isso
private string nome;
public string Nome
{
     get
     {
          return nome;
     }
    
     set
     {
          nome = value;
     }
} 
     Porém esse seria o nosso caso apenas usando SQL/Pascal/VB6. Usando C# isso não se aplica. Esse exemplo é trivial, mas o uso de _ ou de camelCase para os fields privados são os mais usados em C#.
     Nesses casos preceder o campo privado com F (PascalCase) em linguagens derivadas de pascal.

//isso só é permitido em linguagens como Pascal ou Delphi. Não faça isso.
private string Fnome;
public string Nome
{
     get
     {
          return Fnome;
     }
 
     set
     {
          Fnome = value;
     }
}

     Como usaremos apenas C#, vamos prefixar campos de instancia locais com _, como geralmente é feito em linguagens derivadas de C.

//Faça ISSO:
private string _nome;
public string Nome
{
     get
     {
          return _nome;
     }
 
     set
     {
          _nome = value;
     }
}


     Faça:



  • Use PascalCasing para nomear classes, propriedades, métodos e Enuns
  • Use camelCasing para nomear variáveis locais, argumentos de métodos e de constructors
  • Use _camelCasing (camelCase precedido de _) para nomear campos privados
  • Se uma apreviação for fazer parte do nome de uma classe, propriedade ou constante, use PascalCase se ela tiver 3 letras ou mais. Ponha a abreviação em maiúsculas se ela tiver até 2 letras.
  • Use os nomes de tipos predefinidos da linguagem em vez dos System Types, exemplo: strung no lugar de String, int no lugar de Int32, bool no lugar de Boolean
  • Use variáveis implícitas para quando o tipo é óbvio ou não é importante, ou em laços for/foreach. Exemplo: var numero = Convert.ToInt32(Console.ReadLine());
  • Escreva um comando por linha
  • Escreva uma declaração por linha
  • Indente todas as linhas de continuação
  • Quebre uma linha para separar a definição de métodos da definição de propriedades
  • Prefira passar estruturas ou objetos DTO como parâmetros para um método do que listas grandes de parâmetros.
  • Prefira retornar objetos DTO ou estruturas com um propósito específico do que usar parâmetros ref ou out nos seus métodos.
  • Sempre que usar o operador "as " para typecasting seguro verifique se o retorno não é null. Se for null é porque o objeto não pode ser convertido para o tipo especificado.
  • Se você precisa comentar um bloco de código, considere usar refactoring para transformar esse bloco em um método, e de-lhe um nome conciso e apropriado.        
  • Mantenha o tamanho de cada linha em até 130 caracteres (o suficiente para evitar rolagem horizontal)
  • Sempre envolva expressões booleanas de estrutura de repetição ou de decisão do tipo if, else, do, while, for e foreach com parênteses, mesmo que o compilador não exija.


       

     Não faça:



  • Não use notação húngara ou qualquer tipo de definição de tipo nos nomes de variáveis, isso é consistente com o .net framework, além disso o Visual Studio já te ajuda a determinar o tipo das variáveis.
  • Não use maiúsculas para nomear constantes, não é assim que é feito no .net. Maiúsculas atraem muito atenção.
  • Evite abreviações nos nomes de variáveis a não ser que sejam abreviações bem conhecidas como Id, XML, HTML
  • Não insira underscore _ no meio de identificadores, classes, constantes, propriedades e variáveis.
  • Não especifique o tipo de um enum a não ser que seja Flags ou bit fields
  • Não prefixe ou sufixe os nomes de Enum com a palavra "Enum"
  • Não comente código, ninguém saberá o que fazer com código comentado
  • Não use parâmetros de função como variáveis temporárias. O nome do parâmetro não reflete a intenção da variável.
  • Não use números em identificadores de campos ou métodos, como "Listar2". Isso geralmente é sinal de preguiça em encontrar um nome que revele propósito.
  • Não compare expressões booleanas com true ou false em if's. Perguntar if (variavel == true) ou if (variavel == false) prejudica a leitura e revela falta de conhecimento da estrutura if. Use if (variavel) ou if (!variavel).



     Lembre-se:

          A qualidade de um código é medida pelo número de "que diabos ..." por segundo.    
          A honestidade nas pequenas coisas não é uma coisa pequena.
   

Veja mais
Lance Hunt C# Coding Standards
AvSol coding guidelines for C# 3.0-5.0
Java code conventions


Livros
http://www.livrariacultura.com.br/p/domain-driven-design-3252884
http://www.livrariacultura.com.br/p/extreme-programming-15065191
http://www.livrariacultura.com.br/p/codigo-limpo-2874223


Sites
http://www.milfont.org/tech/2008/01/21/nao-use-notacao-estranha/
http://www.macoratti.net/13/09/net_pcod1.htm


Outras fontes
     Há vários padrões de codificação e outras boas práticas de programação. Abaixo exemplifico mais algumas fontes como referência / aprofundamento.
     Há também padrões de codificação para linguagens específicas, elaborados pelas empresas que desenvolvem esses produtos ou pela comunidade de programadores destas linguagens, como os padrões de PHP ou Java exemplificados em anexo.
http://www.dofactory.com/reference/csharp-coding-standards
https://csharpguidelines.codeplex.com/
http://se.inf.ethz.ch/old/teaching/ss2007/251-0290-00/project/CSharpCodingStandards.pdf
https://msdn.microsoft.com/en-us/library/vstudio/ms229043(v=vs.100).aspx




 

segunda-feira, 16 de fevereiro de 2015

Versionamento no .net Framework

Aparentemente a Microsoft tem dois esquemas de versionamento do .net framework. Um é lado a lado, quando há uma mudança da major version. O outro esquema é quando há uma substituição total de um framework, devido a correções de assemblies e lançamentos de novas features.

Por exemplo:

A versão 1 e 1.1 rodam ambas no runtime da 1.
As versões 2, 3 e 3.5 rodam todas no runtime da 2 (aqui a Microsoft fez uma cagada, porque deveria versionar a 3 como 2.5, já que é uma atualização da 2, e a 3.5 deveria continuar a manter a major version como 2, por exemplo 2.5.5, mas eles apressaram as coisas para distribuir junto com o finado windows vista)
E temos a versão 4 e 4.5 que rodam no runtime da 4. A substituição de alguns assemblies da versão 4 pelo instalador da 4.5 pode causar alguns conflitos, principalmente para aplicações que usam novas features exclusivas da 4.5 ou programadores de componentes.

Esses conflitos são citados e podem ser contornados conforme as dicas do Rick Strahl e do SCOTT HANSELMAN

Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel - Trobleshooting

If you get

Could not load type System.ServiceModel.Activation.HttpModule from assembly System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.


error trying to access a webservice in .net framework 4.0 or 4.5 it is caused by a conflict between 3.0 and 4.0 version.

Fortunately you can fix it in a per application basis. You have only to go to IIS Manager, select your application, double click in modules and delete ServiceModel version 3.0 module. 



If you need version 3.0 again or delete 4.0 accidentally you can fix it adding the module again, using the strings below to register the asemblies.

ServiceModel - System.ServiceModel.Activation.HttpModule, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

ServiceModel-4.0 - System.ServiceModel.Activation.ServiceHttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

Enjoy :)

Não foi possível carregar o tipo System.ServiceModel.Activation.HttpModule do assembly System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089. Como Resolver.

Como resolver o erro:

Não foi possível carregar o tipo System.ServiceModel.Activation.HttpModule do assembly System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.


Alguns recursos / módulos e assemblies do .net causam conflitos entre as versões. Esses conflitos são causados pelo IIS, que "Não sabe" qual a versão correta  do assembly carregar, mesmo se o application pool está configurado com a versão correta.

Felizmente essas configurações podem ser corrigidas em cada application no IIS.

Esse erro especificamente é causado por um conflito entre as versões 3.0 e 4.0 do assembly ServiceModel.

Se sua aplicação é em .net 4.0 ou 4.5 e você obteve este erro ao tentar abrir um webservice vá ao IIS (Painel de controle / Ferramentas Administrativas / Gerenciador do Serviços de Informações da Internet (IIS) ), abra o seu site, de um duplo clique em módulos.


Delete o módulo ServiceModel, deixando apenas o ServiceModel-4.0.



Caso você necessite dele novamente, ou tenha apagado o 4.0 por engano, abaixo encontram-se as strings para registrar os assemblies.

System.ServiceModel.Activation.HttpModule, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089


Espero ter ajudado.


segunda-feira, 2 de fevereiro de 2015

Detectar o encoding de um arquivo para não corromper ao transformá-lo

Em uma certa aplicação que eu fiz, um programa windows forms deveria fazer upload de um arquivo CSV para uma aplicação WEB para a importação do arquivo.

O problema é que esse sistema funcionava em 8 línguas diferentes, então, obviamente, o sistema deveria suportar vários tipos de caracteres (e encodings) diferentes.

Além disso o CSV era ou montado por um usuário, exportando do excel, ou exportado por um ERP.

Seria impossível impor ao usuário que sempre gerasse um arquivo UTF-8, porque além de não ser obrigado a saber o que é isso, sistemas ERP legados dificilmente exportarão os arquivos nesse formato, e você, em uma empresa pequena, simplesmente não têm como impor que uma empresa como SAP ou Totvs altere seus sistemas para exportar arquivos em UTF-8.

O site da web aceitava o arquivo como vinha, fazia uma conversão forçada para o formato padrão (ANSI ISO 8859-1) e importava o arquivo.

Conversões forçadas são aquelas que você faz sem saber qual é o formato de origem do arquivo.
Se você simplesmente mandar abrir um arquivo do qual não sabe o encoding o .net framework usará o encoding default do computador. Se o arquivo não estiver na codificação padrão ele será lido erroneamente,  parecendo estar corrompido. Qualquer operação com ele, como concatenar informações e escrever em outro arquivo resultará em strings corrompidas, com acentos perdidos.

Se o arquivo for ASCII ou o ANSI no encoding padrão de um computador com windows em inglês ou português a conversão ocorreria sem problemas. Se o arquivo fosse UTF-8 ou qualquer outro codepage ou tivesse caracteres especiais de uma outra linguagem essa conversão forçada corromperia o arquivo.

Como se isso não bastasse, havia um outro fator: ao subir o arquivo direto pelo site o  browser identificava o encoding do arquivo e colocava o  encoding correto no cabeçalho. Ao ler o stream e colocar o resultado em uma string o .net fazia uma conversão "não forçada" para o formato padrão do servidor, pois ele sabia o formato de origem, informado pelo request, e esse tipo de conversão muda o formato e o encoding de um arquivo sem corromper, mantendo os mesmos caracteres especiais e acentuados, apenas com uma outra codificação.

Isso mascarava o problema, pois raramente ocorria uma corrupção de arquivo.

Por outro lado, quando eu tentava fazer o upload com a aplicação windows, eu tentava impor ou assumir um determinado formato e fazer o upload nesse formato. Um pré processamento era realizado antes do upload do arquivo.

Eu precisava que minha aplicação windows detectasse qual era o encoding do arquivo antes de abri-lo, fizesse as operações necessárias, colocasse esse encoding no cabeçalho http do request e fizesse o upload desse arquivo. O servidor, por sua vez, deveria também detectar o encoding do arquivo, fazer uma asserção para comparar com o encoding informado no request e lançar uma exceção se fossem diferentes. Se fossem iguais ele poderia prosseguir com o processamento do arquivo.

Como se faz para detectar o encoding de um arquivo. Como o browser faz isso?

Uma pesquisada no google me fez encontrar a biblioteca chardet 

Foi necessária a instalação do TortoiseSVN  para baixar o source da mesma, que está no google code. Recompilei o source, e nesse post eu disponibilizo tanto o source como o compilado para download.

Essa biblioteca se propõe a analisar n bytes de um arquivo de texto e fazer uma heurística para descobrir que tipo de encoding ele usa, baseando-se em uma estatística dos caracteres encontrados.

Instale o tortoise SVN, clique com o direito na área de trabalho, dê o comando tortoise checkout e coloque o endereço http://chardetsharp.googlecode.com/svn/trunk/ conforme a figura abaixo.


Abra o sln com o visual studio e recompile o mesmo. Agora você pode importar a dll gerada no seu projeto.

Também encontrei a classe EncodingTools no site do code project, que fazia uso de um outro algoritmo para identificar o encoding. Essa classe, podemos dizer, é mais "caseira".

Encontrei uma classe chamada TextFileEncodingDetector no git que não é muito boa, pois se baseia apenas na detecção do BOM (byte order mark) coisa que a Unicode já descontinuou. Mais sobre Unicode e BOM.

Por último eu encontrei uma biblioteca chamada UDE que na minha opinião foi a mais completa e profissional. Tanto a chardet como a UDE são ports do Mozilla Universal Character Detector no entanto a UDE está mais atual. É a biblioteca usada pela mozilla para detecção de caracteres. A UDE também deve ser baixada com o SVN no endereço e recompilada. No arquivo ao final desse post você pode ver todo

Precisava testar essas três bibliotecas tanto com arquivos de teste como com meus arquivos de produção para saber qual trabalhava melhor. Para testar em casos extremos usei tanto arquivos muito pequenos, com caracteres especiais mas sem caracteres suficientes para caracterizar um determinado encoding / linguagem, e testei com arquivos grandes todos em ANSI e que tinham um único caractere em utf-8.

Eu criei uma classe estática com uma única função para facilitar a transformação de um arquivo em um vetor de bytes. Como meus arquivos são pequenos não fiz nenhuma consideração quanto à performance ou quanto à limitar o tamanho do vetor de bytes. Se você for importar arquivos maiores do que 10mb seria prudente levar isso em consideração.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace EncodeDetector
{
    public static class TextUtils
    {
        public static byte[] FileToBytes(string filename)
        {
            using(FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                byte[] queEuGosto = new byte[fs.Length];
                fs.Read(queEuGosto, 0, (int)fs.Length);
                return queEuGosto;
            }
        }
    }
}

Também criei uma classe chamada VtrEncodingDetector como um wrapper para a biblioteca UDE, com dois objetivos: primeiro, se for detectado que o arquivo tem um BOM, então eu posso identificar o tipo do arquivo lendo apenas 3 ou 4 bytes em vez do arquivo todo. (isso não é confiável, pois o arquivo pode ter uma BOM marcando UTF-8 e estar todo em ANSI). Outro motivo é que eu posso trocar a implementação interna da minha biblioteca assim que eu encontre uma biblioteca de detecção de charset que eu goste mais.
Abaixo o código da classe.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mozilla.CharDet;
using Ude;

namespace EncodeDetector
{
    public static class VtrEncodingDetector
    {
        /// 
        /// Retorna encoding do streaming. (UTF7; UTF8; Unicode)
        /// 
        /// Bytes do Arquivo
        /// Encoding Type
        public static Encoding GetEncodingFile(byte[] bytes)
        {
            if (bytes != null && bytes.Length >= 2)
            {
                //verifica esses primeiro pois utf32 pode confundir-se com utf16
                if (bytes[0] == 0xff && bytes[1] == 0xfe && bytes[2] == 0 && bytes[3] == 0)
                    return Encoding.UTF32;

                //utf-32BE  Unicode (UTF-32 Big-Endian)  
                if (bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0xfe && bytes[3] == 0xff)
                    return Encoding.GetEncoding(12001);

                if (bytes[0] == 255 && bytes[1] == 254)
                    return Encoding.Unicode;

                if (bytes[0] == 0xfe && bytes[1] == 0xff)
                    return Encoding.BigEndianUnicode;


                if (bytes[0] == 239 && bytes[1] == 187 && bytes[2] == 191)
                    return Encoding.UTF8;

                if (bytes[0] == 60 && bytes[1] == 63)
                    return Encoding.ASCII;

                //reconhecimento de utf7 http://pt.wikipedia.org/wiki/Marca_de_ordem_de_byte
                if (bytes[0] == 43 && bytes[1] == 47 && bytes[2] == 118 && (bytes[3] == 56 || bytes[3] == 57 || bytes[3] == 43 || bytes[3] == 47))
                    return Encoding.UTF7;

                //uso da ferramenta UDE para detectar automaticamente o encoding
                try
                {
                    //http://code.google.com/p/ude/
                    CharsetDetector d = new CharsetDetector();
                    d.Feed(bytes, 0, bytes.Length);
                    d.DataEnd();
                    Encoding enc = Encoding.GetEncoding(d.Charset);
                    d.Reset();
                    if (enc != null)
                        return enc;
                    else
                        //por padrão retorna 1252, que é o iso 8859-1, ansi para europa ocidental, codepage padrão para windows em inglês e português
                        //http://msdn.microsoft.com/en-us/library/system.text.encoding.codepage(v=vs.110).aspx
                        //return Encoding.GetEncoding(1252);
                        return Encoding.Default;
                }
                catch
                {
                    //por padrão retorna 1252, que é o iso 8859-1, ansi para europa ocidental, codepage padrão para windows em inglês e português
                    //http://msdn.microsoft.com/en-us/library/system.text.encoding.codepage(v=vs.110).aspx
                    //return Encoding.GetEncoding(1252);
                    return Encoding.Default;
                }
            }
            //por padrão retorna 1252, que é o iso 8859-1, ansi para europa ocidental, codepage padrão para windows em inglês e português
            //http://msdn.microsoft.com/en-us/library/system.text.encoding.codepage(v=vs.110).aspx
            //return Encoding.GetEncoding(1252);
            return Encoding.Default;
        }
    }
}



Antes de começar os testes, abra o projeto do UDE. Você verá que na linha  71 do arquivo UniversalDetector.cs existe um field protected chamado charsetProbers. Um array de Probers.
protected CharsetProber[] charsetProbers = new CharsetProber[PROBERS_NUM];



Vá na classe CharsetDetector, arquivo CharsetDetector.cs, que é descendente de UniversalDetector e transforme esse field protegido em uma propertie somente leitura pública. Poderia ser um método Get também.

public CharsetProber[] CharsetProbers { get { return this.charsetProbers; } }

Isso fará com que a lista interna de CharsetProbers seja exposta, permitindo que você navegue por ela. Um prober é uma classe interna da biblioteca responsável por identificar um charset específico. Quando você cria a classe CharsetDetector e passa para ela um vetor de bytes para serem analisados ela cria vários probers para analisar o arquivo e nesse vetor ficam os probers usados, e a porcentagem de confiabilidade de cada um deles. Como existe um prober para cada encoding, o prober com maior confiabilidade será o que providenciará a resposta final da biblioteca.

Em um projeto real essa modificação é totalmente desnecessária. Foi incluída aqui apenas de curiosidade, para nossos testes. Outra modificação é na biblioteca chardet. Ela sempre escreve o encoding detectado no console, mesmo que você não queira. É um processo interno de debug dela, mas atrapalha quando o teste é feito justamente em uma aplicação console. Você pode comentar a linha 255 do arquivo UniversalDetector.cs:
//Console.Out.WriteLine(aCharset);

Comente também a linha 197 do arquivo SingleByteCharsetProber.cs:
//Console.Out.WriteLine("  SBCS: {0:0.000} [{1}]", GetConfidence(), CharSetName);

Agora a biblioteca não vai mais fazer um dump do seu processo interno de detecção, mesmo em modo debug. A biblioteca UDE também tem essas saídas. Procure por console.writeline ou console.out.writeline dentro de todos os métodos DumpStatus e comente.

E Finalmente a classe Program.cs onde faço os testes. Compile o programa e execute-o pela linha de comando, passando como argumento o nome de um dos arquivos da pasta data ou um que você tenha aí. Mandei junto com o exemplo um executável hexeditor.exe para que você veja como a codificação de um caractere acentuado pode mudar dependendo do encoding usado. Para debugar você pode colocar um nome de arquivo fixo, usar parâmetros da linha de comando, usar testes unitários ou usar um FileOpenDialog. Aproveite também os vários arquivos de testes que vêm com os testes unitários do UDE.

//program


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using href.Utils;
using KlerksSoft;
using Mozilla.CharDet;
using Ude;

namespace EncodeDetector
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Count() == 0)
            {
                Console.WriteLine("Informe um arquivo");
                return;
            }

            //limpa o console
            Console.Clear();
            Console.WriteLine("Econding detectado para o arquivo: " + args[0]);
            Console.WriteLine("");

            //crio o vetor de bytes com o conteudo do arquivo
            byte[] queEuGosto = TextUtils.FileToBytes(args[0]);


            #region encoding tools
            //Usando encoding tools
            //fonte:http://www.codeproject.com/KB/recipes/DetectEncoding.aspx
            Encoding enc1 = EncodingTools.DetectInputCodepage(queEuGosto);
            if (enc1 != null)
                Console.WriteLine("Encoding Tools\t-->\t" + enc1.EncodingName + "/" + enc1.BodyName + "/" + enc1.CodePage);
            #endregion


            #region TextFileEncodingDetector
            //fonte: https://gist.github.com/TaoK/945127
            Encoding enc2 = TextFileEncodingDetector.DetectTextByteArrayEncoding(queEuGosto);
            if (enc2 != null)
                Console.WriteLine("TextFileEncodingDetector\t-->\t" + enc2.EncodingName + "/" + enc2.BodyName + "/" + enc2.CodePage);
            #endregion


            #region chardet
            //fonte: http://code.google.com/p/chardetsharp/
            var d = new UniversalDetector();
            d.HandleData(queEuGosto);
            d.DataEnd();            
            Encoding enc3 = Encoding.GetEncoding( d.DetectedCharsetName);
            if (enc3 != null)
                Console.WriteLine("CharDet\t-->\t" + enc3.EncodingName + "/" + enc3.BodyName + "/" + enc3.CodePage);
            #endregion


            #region UDE via VtrEncodingDetector wrapper
            //usando um wrapper para UDE
            Encoding enc4 = VtrEncodingDetector.GetEncodingFile(queEuGosto);
            if (enc4 != null)
                Console.WriteLine("VtrEncodingDetector\t-->\t" + enc4.EncodingName + "/" + enc4.BodyName + "/" + enc4.CodePage);
            #endregion


            #region UDE direto
            //uando UDE direto
            //fonte: http://code.google.com/p/ude/
            CharsetDetector cdet = new CharsetDetector();
            cdet.Feed(queEuGosto, 0, queEuGosto.Length);
            cdet.DataEnd();
            Encoding enc5 = Encoding.GetEncoding(cdet.Charset);
            if (enc5 != null)
            {
                Console.WriteLine("UDE\t-->\t" + enc5.EncodingName + "/" + enc5.BodyName + "/" + enc5.CodePage + " Com confiabilidade de " + (cdet.Confidence * 100).ToString("0.0") + "%");
                foreach (var x in cdet.CharsetProbers)
                {
                    if (x != null)
                        Console.WriteLine("\t- " + x.GetCharsetName() + "\t" + (x.GetConfidence() * 100).ToString("0.00"));
                }
            }
            cdet.Reset();
            #endregion 


            //só para podermos ver o resultado
            //Console.ReadLine();
        }
    }
}



No arquivo desse exemplo enpacotei junto o source e a versão compilada da UDE e da chardet. Há também alguns arquivos de texto em encodings diferentes para teste.
Divirta-se!

Postagens populares

Marcadores

delphi (60) C# (27) poo (21) Lazarus (19) Site aos Pedaços (15) sql (13) Reflexões (10) Humor (9) javascript (9) .Net (8) ASp.Net (8) api (8) Básico (6) Programação (6) ms sql server (5) banco de dados (4) HTML (3) PHP (3) Python (3) Web (3) design patterns (3) jQuery (3) livros (3) metaprogramação (3) Debug (2) Dicas Básicas Windows (2) Pascal (2) games (2) linguagem (2) música (2) singleton (2) tecnologia (2) Ajax (1) Anime (1) Api do Windows (1) Assembly (1) Eventos (1) Experts (1) GNU (1) Inglês (1) JSON (1) SO (1) datas (1) developers (1) dicas (1) easter egg (1) firebird (1) interfaces (1) introspecção (1) memo (1) oracle (1) reflexão (1)