A importância das boas práticas na programação em .NET
02 de Setembro de 2024
A tecnologia .NET é uma plataforma de desenvolvimento gratuita e de código aberto que permite criar diversos tipos de aplicações, sendo capaz de executar programas escritos em várias linguagens de programação, sendo C# a mais popular entre eles.
A plataforma .NET foi projetada com foco na produtividade, desempenho, segurança e fiabilidade. Além disso, esta tecnologia oferece gestão automática de memória por meio de um coletor de lixo (GC), é segura em termos de tipo e memória e suporta simultaneidade por meio das primitivas async/await e task. As suas bibliotecas estão otimizadas para funcionar em diversos sistemas operacionais e arquiteturas de chip.
Seguir boas práticas em .NET é fundamental para garantir eficiência e segurança dos sistemas
Como melhorar a eficiência
Otimização
Escrever código que seja eficiente em termos de tempo de execução e uso de recursos. Evitar loops desnecessários e alocações excessivas de memória.
Algoritmos e estruturas de dados
Escolher algoritmos apropriados para resolver problemas específicos através de estruturas de dados eficientes, como listas ligadas, árvores e tabelas hash.
Perfil de desempenho
Medir o desempenho do código através de ferramentas de perfil. Identifique pontos de melhorai e otimizá-los.
Como aumentar a segurança
Validação de entrada
Validar entradas de utilizador para evitar ataques como injeção de SQL ou cross-site scripting (XSS).
Tratamento de erros
Lidar com erros de forma adequada evitando informações sensíveis em mensagens de erro.
Criptografia
Proteger dados confidenciais com criptografia adequada.
Privilégios mínimos
Garantir que o código executado com os privilégios mínimos necessários.
Boas práticas gerais para a programação em .NET
Nomes descritivos
Usar nomes descritivos e objetivos para variáveis, funções e classes. Os nomes devem ser claros o suficiente para indicar a sua finalidade sem a necessidade de comentários adicionais.
Comentários
Documentar o código com comentários claros e concisos. Utilizar comentários XML para gerar documentação automática do código, tornando-a acessível para outras ferramentas e developers.
Gestão de versões
Usar sistemas de controlo de versões para gerir alterações. Ferramentas como GitHub, GitLab e Bitbucket oferecem funcionalidades adicionais de colaboração e integração contínua.
Testes unitários
Escrever testes para verificar a funcionalidade do código.
Revisão de código
Requisitar a outros elementos para rever o código desenvolvido.
Organização de código em .NET
As convenções de nomenclatura são essenciais para um desenvolvimento de software eficiente e colaborativo, na medida em que facilitam a leitura do código por outros programadores e evita potenciais ambiguidades por torná-lo mais compreensível.
Uma colaboração em que as boas práticas em .NET são asseguradas facilita a manutenção e garante a melhoria contínua do código, aumentando a produtividade das equipas.
Em C# podemos usar PascalCase ou camelCase
PascalCase
Utiliza-se PascalCase para nomes de tipos (classes, structs, enums), métodos públicos, propriedades e nomes de arquivos. A primeira letra de cada palavra é maiúscula, e as restantes minúsculas.
Exemplos:
- - MinhaClasse
- - CalcularSalario
- - NomeCompleto
camelCase
Utiliza-se camelCase para variáveis locais, parâmetros de método e campos privados. A primeira letra é minúscula, e as restantes palavras começam com letra maiúscula.
Exemplos:
- - idade
- - nomeCompleto
- - quantidadeItens
Estrutura de pastas e arquivos
A organização de arquivos e pastas em projetos .NET é fundamental para manter a clareza, facilitar a manutenção e melhorar a colaboração entre programadores.
Algumas boas práticas incluem:
Manter projetos separados
Essa abordagem envolve manter projetos de .NET framework e outros existentes em pastas separadas, evitando a necessidade de utilizar o Visual Studio para abrir projetos antigos. Cada projeto permanece independente e não há variação de código entre eles. É uma boa opção para dar suporte a programadores que trabalham com estruturas mais antigas e não têm a versão mais recente do Visual Studio.
Projeto .NET multiplataforma
Uma alternativa é reorganizar o repositório para ter apenas um arquivo ".csproj" direcionado a várias estruturas, permitindo compilar o projeto para diferentes plataformas e lidar com opções de compilação específicas para cada estrutura.
Pastas principais em projetos ASP.NET Core MVC
Além das pastas principais, como "Controllers", "Models" e "Views", existem outros arquivos e pastas importantes num projeto ASP.NET Core MVC:
- - "appsettings.json": contém configurações da aplicação.
- - "Startup.cs": configura o pipeline de solicitação e define os serviços da aplicação.
- - "Program.cs": inicia a aplicação.
É importante adaptar a organização às necessidades específicas de cada projeto e equipa. Manter a consistência e documentar as decisões irão facilitar a colaboração futura.
A separação lógica de diferentes componentes é essencial para manter um código organizado e facilitar a manutenção. Para esse efeito, as melhores práticas são:
Módulos e classes
Dividir o código em módulos ou classes que representem diferentes funcionalidades ou responsabilidades. Por exemplo, numa aplicação web, classes separadas para manipulação de bases de dados, autenticação, lógica de negócio e interface do utilizador.
Camadas de arquitetura
Utilizar uma arquitetura em camadas, como a arquitetura MVC (Model-View-Controller) ou MVVM (Model-View-ViewModel), onde cada camada tem uma função específica:
- - Model: representa os dados e a lógica de negócio.
- - View: lida com a interface do usuário.
- - Controller/ViewModel: gere a comunicação entre o Model e a View.
Interfaces e contratos
Definir interfaces para componentes que se precisam de comunicar entre si, permitindo que diferentes implementações sejam utilizadas sem afetar o restante do código.
Injeção de dependência
Utilizar a injeção de dependência para fornecer componentes externos (como serviços ou bibliotecas) aos seus módulos, facilita a substituição de implementações e melhora a testabilidade.
Organização de pastas
Separar os arquivos em pastas com nomes significativos, como por exemplo:
- - "Controllers": para controladores numa aplicação web.
- - "Services": para serviços de negócio.
- - "Models": para classes de modelo de dados.
Escrever código limpo em .NET
Princípios SOLID
Os princípios SOLID são um conjunto de diretrizes para o design de software orientado a objetos cujo objetivo passa por melhorar a legibilidade, manutenção e flexibilidade do código.
A sigla SOLID representa os cinco princípios essenciais
SRP (Princípio da responsabilidade única)
Uma classe deve ter apenas uma razão para mudar. Cada classe deve ter apenas uma tarefa específica.
OCP (Princípio aberto/fechado)
Os módulos devem ser abertos para extensão, mas fechados para modificação, o que possibilita adicionar funcionalidades sem alterar o código existente.
LSP (Princípio da substituição de Liskov)
Objetos de uma superclasse devem ser substituíveis por objetos de uma subclasse sem afetar o funcionamento do programa.
ISP (Princípio da segregação de interfaces)
As interfaces não devem conter métodos desnecessários para evitar sistemas pesados.
DIP (Princípio da inversão de dependências)
Dependências devem ser baseadas em abstrações, não em implementações concretas.
Revisão contínua
A revisão de código em C# é uma prática crucial para manter a qualidade e a legibilidade do software. Algumas das razões pelas quais a revisão de código é importante deve-se sobretudo à facilidade de manutenção, eliminação de redundâncias, reutilização de código, otimização de desempenho e a procura de bugs e vulnerabilidades.
Técnicas comuns de revisão
Extrair métodos
Passa por identificar trechos de código repetidos ou complexos e criar um método para encapsular essa lógica, o que melhora a legibilidade e facilita a manutenção do projeto.
Renomear variáveis e métodos
Escolher nomes descritivos para variáveis e métodos, evitando abreviações e nomes genéricos, torna o código mais compreensível.
Eliminar duplicação de código
Procurar por duplicações e extrai-las para métodos ou classes, garantindo que a mesma lógica não seja repetida em vários lugares.
Simplificar condições
Reduzir a complexidade de declarações condicionais utilizando operadores lógicos e expressões mais simples.
Dividir classes grandes
Classes grandes são difíceis de entender e manter, por isso é importante separar funcionalidades em classes menores e coesas.
Melhorar a organização
Para facilitar a navegação no código, é necessário agrupar métodos relacionados e organizá-los logicamente.
Remover parâmetros não utilizados
Identificar e eliminar parâmetros desnecessários em métodos para simplificar o código.
Gerir dependências em .NET
A injeção de dependências é um padrão de projeto utilizado em engenharia de software para reduzir o acoplamento entre as classes de um sistema. Por definição, a injeção de dependência permite que as classes dependam de abstrações (como interfaces ou classes base) em vez de dependerem diretamente de implementações concretas, estruturando o código de forma que as classes recebam as suas dependências externamente, em vez de criá-las internamente.
Exemplo de gestão de dependências
Num cenário hipotético, vamos imaginar uma classe “Carro” que possui várias dependências, como rodas, motor e etc. Sem injeção de dependências, a classe “Carro” seria responsável por criar todas estas dependências. No entanto, com a injeção de dependências, podemos mudar as rodas em tempo de execução, injetando diferentes implementações de rodas sem modificar a classe “Carro”. Isto torna o código mais flexível, testável e independente das implementações concretas.
//Abstração da Dependência
public interface IMinhaDependencia
{
void WriteMessage(string message);
}
//Registo de Serviços
services.AddSingleton<IMinhaDependencia, MinhaDependencia>();
//Injeção no Construtor
public class IndexController : Controller
{
private readonly IMinhaDependencia _dependencia;
public IndexController(IMinhaDependencia dependencia)
{
_dependencia = dependencia;
}
public IActionResult Index()
{
_dependencia.WriteMessage("Chamada a ação Index ");
return View();
}
}
Implementação de testes em .NET
Testes unitários
Os testes unitários servem para proteger o código, ajudando a manter a qualidade e a confiança em todo o processo de desenvolvimento, sendo importantes para:
Deteção precoce de problemas
Os testes unitários verificam partes específicas do código, como funções e métodos de forma isolada, permitindo identificar erros e problemas antes que se propaguem para outras partes do sistema.
Manutenção e revisão de código
Quando é necessário proceder a alterações no código, os testes unitários fornecem uma rede de segurança. Se algum erro existir, é conhecido de forma imediata. Também permitem reescrever código com mais confiança, sabendo que os testes verificarão se o sistema continua funcional.
Documentação proativa
Testes unitários servem como documentação ao projeto, mostrando como as partes individuais do código devem comportar-se.
Facilitam a colaboração
Quando vários programadores contribuem para um projeto, os testes unitários ajudam a garantir que todos estão em sintonia, definindo expectativas claras sobre como o código deve funcionar, independentemente de quem o escreveu.
Aumentam a confiança
Os testes unitários aumentam a confiança à medida que o projeto é desenvolvido, permitindo que se saiba que as alterações feitas na linha do tempo não alterarão funcionalidades existentes.
Testes de integração
Os testes de integração são uma etapa essencial no desenvolvimento de software, onde módulos ou componentes são combinados e testados em grupo. O objetivo é verificar a eficiência e a segurança da comunicação entre sistemas, garantindo que o software funciona sem erros de integração. Esta fase ocorre após os testes de unidade (onde os módulos são testados individualmente) e antes dos testes de sistema (que avaliam o sistema completo num ambiente simulado de produção).
Os testes de integração podem ser manuais ou automatizados, e são colaborativos, envolvendo programadores, testers e engenheiros de software. Eles ajudam a identificar e corrigir problemas de interface, como dados perdidos, erros de sintaxe ou semântica nos dados, garantindo a qualidade da aplicação.
Diferença entre testes unitários e testes de integração
Os testes unitários têm como foco as partes individuais do código, como funções, métodos ou classes, para verificar se cada unidade funciona corretamente de forma isolada, simulando ou substituindo dependências externas. São rápidos, pois testam pequenas partes do código. Um exemplo seria testar uma função que calcula a média de uma lista de números. Em contraste, os testes de integração avaliam a interação entre diferentes partes do sistema para assegurar que os componentes comunicam corretamente entre si e integram-se sem problemas. Estes testes ocorrem num ambiente mais próximo do ambiente de produção e são, por norma, mais lentos, pois envolvem várias partes do sistema. Um exemplo seria testar a integração entre um serviço web e uma base de dados.
Segurança no desenvolvimento em .NET
Ao desenvolver em .NET, é fundamental seguir práticas de segurança para proteger o código e garantir a integridade do sistema, tais como:
- - Não utilizar CAS (Segurança de acesso do código).
- - Evitar código parcialmente fiável.
- - Código de segurança neutra
- - Realizar análises de código estático
- - Realizar revisões de código, tanto de forma manual como automatizada
- - Realizar testes baseados em requisitos para verificar a segurança do código.
Ferramentas e técnicas para escrever código seguro em .NET
Análise SAST (Static Application Security Testing)
O SAST analisa o código em busca de vulnerabilidades antes da compilação. As ferramentas SAST fornecem feedback em tempo real aos developers, ajudando-os a corrigir problemas antes de avançar no ciclo de desenvolvimento, dando destaque às vulnerabilidades no código, oferecendo orientações sobre como corrigi-las.
Checklist OWASP (Open Web Application Security Project)
Oferece uma lista de verificação com práticas de segurança em todas as camadas da aplicação. Consultar o OWASP checklist ajuda a garantir são considerados os aspetos como identidade, autenticação, controlo de acesso, validação de entrada, criptografia, gestão de utilizadores e tratamento de erros.
Testes de segurança
Validam se o código segue os padrões de segurança através da verificação de funções, métodos e bibliotecas antes de integrá-los à aplicação. Estes testes garantem que as alterações de código exigidas pelas revisões de segurança são implementadas corretamente.
Gestão de autenticação e autorização
Implementar autenticação e autorização seguras em aplicações .NET é crucial para proteger os sistemas e respetivos dados.
Autenticação e autorização no ASP.NET Web API
Oferece suporte à autenticação e autorização. A autorização determina se um utilizador tem permissão para executar uma ação específica. A autenticação pode ser configurada utilizando módulos integrados do IIS ou escrever um módulo HTTP personalizado. Além disso, os manipuladores de mensagens HTTP podem ser usados para autenticação específica da API Web.
Biblioteca Identity no ASP.NET Core
Trata-se de uma biblioteca poderosa para a autenticação e autorização, oferecendo suporte à autenticação externa (como logins do Google ou Facebook) e autenticação de dois fatores. Com o Identity, é possível gerir utilizadores, papéis e permissões de forma eficiente.
Boas práticas de segurança em aplicações .NET
Validação de entrada, proteção contra ataques comuns (como SQL Injection e Cross-Site Scripting) e gestão adequada de vulnerabilidades são boas práticas de segurança a serem seguidas.
Como otimizar o desempenho das aplicações .NET
Evitar conversões boxing e unboxing
Evitar usar tipos de valor em situações que exigem conversão boxing repetida, como em coleções não genéricas (por exemplo, System.Collections.ArrayList). As conversões boxing e unboxing são processos computacionalmente complexos, pois criam objetos, sendo preferível optar por atribuições de referência simples sempre que possível.
Utilizar StringBuilder
Para concatenar muitas variáveis de cadeias de caracteres (por exemplo, em loops), é preferível utilizar System.Text.StringBuilder em vez do operador + em C# para melhorar a eficiência.
Evitar finalizadores vazios
Quando uma classe contém um finalizador, cria uma entrada na fila. Se o finalizador estiver vazio, isso irá resultar numa perda de desempenho, sendo benéfico remover finalizadores desnecessários.
Monitorizar perfis
As ferramentas de perfil servem para identificar constrangimentos no desempenho, o que ajuda a identificar pontos passíveis de serem otimizados. Monitorizar o uso de memória, CPU e outros recursos permite identificar áreas que carecem de intervenção.
Testar
Os testes de desempenho permitem verificar se as otimizações estão a funcionar conforme o pretendido. Caso necessário, reescrever o código permite melhorar algoritmos, reduzir loops desnecessários e otimizar consultas à base de dados.
Documentação de código em .NET
A documentação de código é uma prática fundamental e muitas vezes subestimada, essencial para a prosperidade dos projetos tecnológicos. Documentar código é um passo importante para o êxito a longo prazo de um programador de software na área de .NET.
Benefícios de documentar código
Facilita a manutenção
A documentação simplifica a compreensão do funcionamento do código, acelerando o processo de manutenção e evolução projeto. Assim, os programadores que necessitem fazer alterações no código existente podem rapidamente entender a estrutura e a lógica com a ajuda da devida documentação.
Transmissão de conhecimento
Quando novos programadores assumem uma função num projeto, a documentação serve como ferramenta para transmitir conhecimentos, o que, na prática, reduz o tempo necessário para que para se familiarizem com o código e o ambiente de desenvolvimento.
Reutilização de código
Projetos bem documentados incentivam a reutilização de código, pois os developers podem identificar funções úteis em projetos anteriores e incorporá-los em novos projetos com facilidade, poupando recursos.
Garantia de qualidade
Documentar o código também contribui para a qualidade do projeto. Quando os programadores comentam os projetos, são incentivados a pensar sobre o design e a funcionalidade, resultando num código mais eficiente.
Colaboração eficiente
Em ambientes de desenvolvimento colaborativo, a documentação de código promove uma comunicação eficaz. Assim, as equipas podem entender o que cada parte do código faz e coordenar melhor as suas tarefas.
Ferramentas para documentar código de projetos automaticamente
PlantUML
Esta ferramenta permite utilizar linguagem escrita para criar diagramas. É uma forma eficiente de visualizar a arquitetura e os fluxos existentes no software.
JSDoc
Especialmente para projetos JavaScript, o JSDoc é útil para gerar automaticamente documentação a partir do código-fonte. Cria documentação legível tanto para humanos quanto para ferramentas.
Postman
Embora seja conhecida principalmente como uma ferramenta para testar APIs, Postman também pode gerar documentação através da sua API, facilitando a descrição dos endpoints, parâmetros e respostas.
Manutenção e atualizações regulares de projeto .NET
As atualizações de código são fundamentais para garantir que um sistema permanece eficiente ao longo do tempo, sendo que algumas das práticas de manutenção incluem:
Revisão de código
A revisão é uma forma de reorganizar o código, melhorando a sua estrutura interna sem alterar o comportamento dinâmico. Esta prática ajuda a manter o código limpo e legível. É importante avaliar os testes necessários e realizar eventuais acréscimos de código de forma gradual.
Nomes intuitivos
Usar nomes descritivos para variáveis e funções torna o código mais fácil de entender e manter.
Linhas breves
É boa prática manter as linhas de código curtas e remover o código desnecessário ou obsoleto, melhorando a legibilidade e o desempenho da solução.
Comentários
Registar comentários no projeto para explicar o que o código está a produzir e a sua lógica ajuda outros programadores a entenderem o contexto.
Indentação consistente
Manter um padrão de espaçamento e indentação facilita a leitura do código e conduz a uma uniformização no processo de desenvolvimento.
Divisão em funções e módulos
Para melhorar a organização e facilitar a manutenção, é sugerido que se separe o código em funções e módulos bem definidos.
Sistemas de controlos de versões
Utilizar controladores de versões como o Git ou TFS, ajudam a manter as alterações no código registadas e de fácil recuperação ou comparação.
Em suma, adotar boas práticas na programação em .NET é fundamental para desenvolver soluções tecnológicas com maior desempenho. As otimizações no projeto produzem uma melhoria da legibilidade a colaboração, ao passo que o uso de ferramentas garante um acompanhamento eficaz ao longo do todo o desenvolvimento. Assim, a implementação destas práticas resulta em projetos mais eficientes, beneficiando não só os programadores como os utilizadores finais.
Artigos populares
O que é Outsystems e como está a transformar o desenvolvimento de software
Java - O que torna esta linguagem tão popular entre programadores?
Linux: um sistema operacional versátil e poderoso
COBOL - Será esta linguagem relevante nos dias de hoje?
Implementar um sistema de Business Intelligence
Desafios Emergentes na Segurança em IT - Tendências e Estratégias para proteger as Empresas