Author Archives: Luis Fernando Chaim

Entendendo SOLID

Entendendo S.O.L.I.D.: Fundamentos para um Design de Software Eficaz

Introdução

Os princípios S.O.L.I.D. são um conjunto de diretrizes para o design de software orientado a objetos que ajudam a fazer sistemas mais compreensíveis, flexíveis e mantíveis. Criados e popularizados por Robert C. Martin (também conhecido como “Uncle Bob”), esses princípios têm como objetivo melhorar a legibilidade do código, facilitar a manutenção e permitir que o software evolua com menos riscos de introduzir bugs. Este eBook explora cada um dos princípios com detalhes, auxiliando desenvolvedores a aplicá-los efetivamente em projetos sem recorrer a exemplos de código.

1. Princípio da Responsabilidade Única (Single Responsibility Principle – SRP)

Definição e Importância

O Princípio da Responsabilidade Única estabelece que uma classe deve ter uma, e somente uma, razão para mudar. Isso significa que cada classe deve ser responsável por uma única parte da funcionalidade fornecida pelo software. A ideia central é que acoplar múltiplas responsabilidades em uma única classe aumenta o risco de alterações em uma área afetarem outras, levando a bugs e dificuldades na manutenção.

Aplicações Práticas

  • Modularidade: Promove a divisão clara de funcionalidades, facilitando o entendimento e a localização de funcionalidades específicas.
  • Manutenção Facilitada: Reduz complexidade, permitindo alterações sem afetar áreas não relacionadas.
  • Teste Unitário Simplificado: Cada classe, com sua responsabilidade única, torna-se mais fácil de testar.

2. Princípio do Aberto/Fechado (Open/Closed Principle – OCP)

Definição e Importância

O Princípio do Aberto/Fechado declara que o software deve ser aberto para extensão, mas fechado para modificação. Isso significa que a funcionalidade de uma classe deve ser estendida sem alterar seu código existente, promovendo a criação de sistemas que podem crescer com o tempo sem sacrificar estabilidade.

Aplicações Práticas

  • Extensibilidade: Permite adicionar novas funcionalidades através de novas classes ou métodos, preservando o comportamento do código existente.
  • Segurança do Código: Minimiza os riscos de modificar código testado e em produção.
  • Evolução do Projeto: Facilita a adaptação a requisitos futuros sem reescrever componentes existentes.

3. Princípio da Substituição de Liskov (Liskov Substitution Principle – LSP)

Definição e Importância

Segundo o Princípio da Substituição de Liskov, objetos de uma classe devem poder ser substituídos por objetos de suas subclasses, sem alterar a correção do programa. Este princípio assegura que uma classe derivada pode ser usada no lugar de sua classe base sem comprometer a funcionalidade.

Aplicações Práticas

  • Polimorfismo: Garante que o comportamento do sistema permaneça consistente usando subclasses.
  • Flexibilidade no Design: Facilita a implementação de novas funcionalidades através de hierarquias de classes.
  • Consistência e Robustez: Evita a violação de contratos estabelecidos por classes base, promovendo sistemas mais confiáveis.

4. Princípio da Segregação de Interface (Interface Segregation Principle – ISP)

Definição e Importância

O Princípio da Segregação de Interface sugere que os clientes não devem ser forçados a depender de interfaces que não utilizam. Isso implica que é melhor criar interfaces mais específicas e focadas, ao invés de usar interfaces “gordas” e genéricas.

Aplicações Práticas

  • Clareza: Assegura que as interfaces estão claramente definidas e são relevantes para seus consumidores.
  • Redução de Dependências: Minimiza o acoplamento entre componentes do sistema, permitindo maior flexibilidade para mudanças.
  • Manutenabilidade: Torna o sistema mais fácil de refatorar e evoluir sem modificar desnecessariamente classes não relacionadas.

5. Princípio da Inversão de Dependência (Dependency Inversion Principle – DIP)

Definição e Importância

O Princípio da Inversão de Dependência afirma que módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações, que não devem depender de detalhes. Este princípio incentiva o uso de interfaces ou classes abstratas para desacoplar componentes do sistema.

Aplicações Práticas

  • Desacoplamento: Facilita o isolamento de diferentes camadas do software, promovendo uma arquitetura mais limpa.
  • Testabilidade: Simplifica o processo de teste, permitindo a substituição fácil de implementações reais por mocks ou stubs.
  • Flexibilidade e Reutilização: Torna mais fácil substituir componentes sem alterar o código existente.

Conclusão

Os princípios S.O.L.I.D. são fundamentais para a construção de software robusto, escalável e fácil de manter. Ao aplicar esses princípios, os desenvolvedores podem criar sistemas mais modulares e adaptáveis às mudanças, garantindo que os investimentos em software sejam protegidos ao longo do tempo. Em um cenário de desenvolvimento contínuo, onde mudanças e requisitos novos são constantes, compreender e aplicar S.O.L.I.D. é essencial para garantir a longevidade e o sucesso dos projetos de software.

Próximos Passos

Para aprofundar seu conhecimento e aplicar eficazmente os princípios S.O.L.I.D., considere as seguintes ações:

  • Estudo Continuado: Explore livros e cursos que abordam design de software orientado a objetos.
  • Discussões e Comunidades: Participe de fóruns e grupos de discussão para trocar experiências e aprender com outros profissionais.
  • Prática Constante: Almeje implementar esses princípios em projetos reais, buscando sempre revisar e refatorar o código para melhor aderência aos princípios.

Espero que este eBook sirva como um guia útil e inspirador em sua jornada no desenvolvimento de software de alta qualidade!

Padrões GoF

Padrões de Design do GoF

Categorias Principais

  1. Padrões Criacionais: Focam na criação de objetos
    • Singleton: Garante uma única instância de uma classe
    • Factory Method: Cria objetos sem especificar a classe exata
    • Abstract Factory: Cria famílias de objetos relacionados
    • Builder: Constrói objetos complexos passo a passo
    • Prototype: Cria novos objetos clonando existentes
  2. Padrões Estruturais: Lidam com composição de classes e objetos
    • Adapter: Converte interface de uma classe em outra esperada
    • Bridge: Separa uma abstração de sua implementação
    • Composite: Compõe objetos em estruturas de árvore
    • Decorator: Adiciona responsabilidades a objetos dinamicamente
    • Facade: Fornece interface simplificada para um subsistema
    • Flyweight: Compartilha objetos para economizar memória
    • Proxy: Fornece um substituto para outro objeto
  3. Padrões Comportamentais: Descrevem comunicação entre objetos
    • Chain of Responsibility: Passa uma solicitação ao longo de uma cadeia de handlers
    • Command: Transforma uma solicitação em um objeto independente
    • Interpreter: Implementa uma linguagem especializada
    • Iterator: Acessa elementos de uma coleção sequencialmente
    • Mediator: Reduz dependências diretas entre objetos
    • Memento: Captura e restaura o estado interno de um objeto
    • Observer: Define uma dependência um-para-muitos entre objetos
    • State: Permite que um objeto altere seu comportamento
    • Strategy: Define uma família de algoritmos intercambiáveis
    • Template Method: Define o esqueleto de um algoritmo
    • Visitor: Separa um algoritmo de uma estrutura de objetos

Referência

Livro: “Design Patterns: Elements of Reusable Object-Oriented Software” Autores: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides Publicado em 1994

Princípio Fundamental

Os padrões de design visam tornar o código mais flexível, reutilizável e manutenível, resolvendo problemas recorrentes no desenvolvimento de software orientado a objetos.

ChatGPT vs Perplexity vs Claude

Prompt

What’s top 5 books that cover test coding algorithms?

ChatGPT

Here are five highly regarded books on coding algorithms and techniques for mastering technical interviews and test coding:

1. “Introduction to Algorithms” by Cormen, Leiserson, Rivest, and Stein (CLRS)

  • Why it’s great: A comprehensive resource on algorithms, covering a wide array of topics with detailed explanations and proofs. It’s excellent for building a deep understanding.
  • Best for: Those who want a solid theoretical foundation.

2. “The Algorithm Design Manual” by Steven Skiena

  • Why it’s great: Combines theory with practical applications, including case studies and problem-solving tips. The “War Stories” section gives real-world examples.
  • Best for: Engineers looking for a mix of theory and real-world application.

3. “Cracking the Coding Interview” by Gayle Laakmann McDowell

  • Why it’s great: Focused on preparing for coding interviews, with 189 programming questions and detailed solutions.
  • Best for: Beginners and job seekers who want to focus on interview preparation.

4. “Elements of Programming Interviews” by Adnan Aziz, Tsung-Hsien Lee, and Amit Prakash

  • Why it’s great: Covers a wide variety of problems, with clear explanations and solutions. Includes a companion volume with code snippets in different languages.
  • Best for: Those looking for structured interview preparation with a strong focus on problem-solving.

5. “Grokking Algorithms” by Aditya Bhargava

  • Why it’s great: Uses visuals and simple language to explain complex topics. Ideal for beginners.
  • Best for: Visual learners and those new to algorithms.

Each of these books caters to different learning styles and needs. If you’re preparing for interviews, start with Cracking the Coding Interview or Elements of Programming Interviews. For deeper theoretical knowledge, go with CLRS or Skiena.

Perplexity

Here are five highly recommended books that cover coding algorithms, ideal for those looking to enhance their understanding and skills in algorithmic problem-solving:

1. Introduction to Algorithms

  • Authors: Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein
  • Overview: This comprehensive textbook serves as both a reference and a guide to algorithms, covering a wide range of topics with detailed explanations, diagrams, and proofs. It assumes some familiarity with mathematics and data structures, making it suitable for intermediate learners

2. Data Structures and Algorithms Made Easy: Data Structures and Algorithmic Puzzles

  • Author: Narasimha Karumanchi
  • Overview: This book is particularly useful for those preparing for coding interviews or exams. It provides numerous algorithm problems along with their solutions, emphasizing practical application and understanding of data structures

3. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People

  • Author: Aditya Bhargava
  • Overview: A beginner-friendly book that uses illustrations to explain algorithms in a clear and engaging manner. It covers essential algorithms such as sorting and searching, making it accessible to those who may struggle with traditional texts

4. Code Complete

  • Author: Steve McConnell
  • Overview: This classic book focuses on software construction principles and practices, including coding techniques that enhance the quality of code. It provides insights into debugging and testing, making it a valuable resource for both novice and experienced programmers

5. The Pragmatic Programmer

  • Authors: Andrew Hunt and David Thomas
  • Overview: This influential book covers a variety of programming topics, including best practices in coding, debugging strategies, and effective collaboration techniques. It is designed to help programmers develop practical skills that can be applied in real-world scenarios

These books not only cover algorithmic concepts but also provide practical insights into coding practices that can significantly improve your programming capabilities.

Claude

Here are five highly recommended books for learning about testing and coding algorithms:

  1. “Cracking the Coding Interview” by Gayle Laakmann McDowell This book is a comprehensive guide that not only covers algorithm design and testing but also provides extensive interview preparation. It includes 189 programming interview questions and solutions, making it invaluable for understanding how to approach algorithmic challenges and test your code effectively.
  2. “Introduction to Algorithms” by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein (often referred to as CLRS) Considered the “bible” of algorithms, this book provides in-depth coverage of algorithm design and analysis. While not exclusively about testing, it offers rigorous approaches to understanding and verifying algorithmic correctness, which is crucial for effective testing.
  3. “Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martin Although not strictly an algorithms book, this text is essential for understanding how to write testable code. It provides principles for writing clean, maintainable code that is inherently easier to test, with detailed insights into writing robust and reliable algorithms.
  4. “Algorithms” by Robert Sedgewick and Kevin Wayne This book provides a comprehensive approach to algorithms with a strong emphasis on implementation and testing. It covers both the theoretical and practical aspects of algorithm design, with numerous examples and implementation strategies that highlight the importance of thorough testing.
  5. “Test-Driven Development: By Example” by Kent Beck While this book focuses on the test-driven development (TDD) methodology, it provides invaluable insights into how to approach algorithm design through comprehensive testing. Beck demonstrates how to write tests before implementing code, which is particularly useful for algorithmic problem-solving.

Each of these books offers a unique perspective on algorithms and testing, providing readers with a well-rounded understanding of how to develop, implement, and verify algorithmic solutions. They range from practical interview preparation to deep theoretical understanding, catering to different levels of expertise and learning styles.

For those seriously interested in mastering algorithm design and testing, I recommend reading multiple books from this list, as each provides complementary insights into writing efficient, correct, and testable code.

 

Plano Treinamento – Software Developer

Plano de Treinamento em Desenvolvimento de Software

Módulo 1: Princípios SOLID

  • Conteúdo: Estudo dos cinco princípios SOLID:
    • Single Responsibility Principle
    • Open-Closed Principle
    • Liskov Substitution Principle
    • Interface Segregation Principle
    • Dependency Inversion Principle
  • Livros:
  • Recursos:
    • Artigos e tutoriais sobre SOLID.

Módulo 2: Clean Code

Módulo 3: Separation of Concerns (SoC)

Módulo 4: Test-Driven Development (TDD)

Módulo 5: Design Patterns

Módulo 6: Refactoring

Módulo 7: DevOps e CI/CD

Módulo 8: Estratégias de Deployment

  • Conteúdo: Shadow, Canary, Rolling Update, Blue Green.
  • Recursos:
    • Artigos e estudos de caso sobre estratégias de deployment.

Módulo 9: Provas de Conceito (POCs) e Spikes

  • Conteúdo: Desenvolvimento de POCs para validação de tecnologias.
  • Recursos:
    • Workshops práticos.

Módulo 10: Transaction Stream / Data Stream

Módulo 11: OpenShift e AWS

Módulo 12: Segurança e Padrões de Logs

Módulo 13: Automação e RPA

  • Conteúdo: Criação de automações e pipelines.
  • Recursos:

Módulo 14: Arquitetura Avançada

  • Conteúdo: Micro serviços, DDD, Arquitetura Hexagonal, CQRS.
  • Livros:
  • Recursos:
    • Estudos de caso sobre arquiteturas modernas.

Módulo 15: Integração de APIs

Conclusão

Este plano de treinamento é uma base sólida para desenvolver as habilidades necessárias em desenvolvimento de software moderno. É importante adaptar o conteúdo e a abordagem de acordo com as necessidades específicas do grupo ou indivíduo em treinamento. Além disso, a prática contínua e a participação em projetos reais são essenciais para consolidar o aprendizado.

Design Interview 80-20

𝟭. 𝗦𝗰𝗮𝗹𝗮𝗯𝗹𝗲 𝗗𝗮𝘁𝗮 𝗦𝘁𝗼𝗿𝗮𝗴𝗲
• Relational vs. NoSQL: Know when to use SQL vs. NoSQL databases.
• Partitioning: Vertical and horizontal partitioning (sharding). Understand trade-offs.
• Indexing: Covering indexes, primary vs. secondary indexes.
• Consistency Models: Strong, eventual, causal.

𝟐. 𝐂𝐚𝐜𝐡𝐢𝐧𝐠
• Client-side vs. Server-side Cache: Understand where caching should happen.
• Caching Strategies: Write-through, write-back, write-around.
• Distributed Cache: Redis, Memcached.
• Cache Eviction Policies: LRU, LFU, etc.

𝟑. 𝐋𝐨𝐚𝐝 𝐁𝐚𝐥𝐚𝐧𝐜𝐢𝐧𝐠
• Horizontal Scaling: Why and how to horizontally scale services.
• Load Balancing Techniques: Round-robin, consistent hashing.
• Reverse Proxy: Understand how to use Nginx, HAProxy.

𝟒. 𝐀𝐬𝐲𝐧𝐜𝐡𝐫𝐨𝐧𝐨𝐮𝐬 𝐏𝐫𝐨𝐜𝐞𝐬𝐬𝐢𝐧𝐠
• Message Brokers: Kafka, RabbitMQ. When to use queues vs. streams.
• Event-Driven Architecture: Benefits of decoupling and event sourcing.
• Task Queues: For delayed jobs or retries.

𝟓. 𝐃𝐚𝐭𝐚𝐛𝐚𝐬𝐞 𝐑𝐞𝐚𝐝 𝐚𝐧𝐝 𝐖𝐫𝐢𝐭𝐞 𝐒𝐜𝐚𝐥𝐢𝐧𝐠
• Read Scaling: Master replication, read replicas.
• Write Scaling: Challenges with partitioning for writes, leader-election.
• CAP Theorem: Consistency, Availability, or Partition tolerance may be compromised.

𝟲. 𝗗𝗶𝘀𝘁𝗿𝗶𝗯𝘂𝘁𝗲𝗱 𝗦𝘆𝘀𝘁𝗲𝗺𝘀 𝗖𝗼𝗻𝗰𝗲𝗽𝘁𝘀
• Consensus Algorithms: Paxos, Raft.
• Conflict Resolution: Last Write Wins, CRDTs, vector clocks for data reconciliation.

𝟳. 𝗥𝗲𝗹𝗶𝗮𝗯𝗶𝗹𝗶𝘁𝘆 𝗮𝗻𝗱 𝗙𝗮𝗶𝗹𝗼𝘃𝗲𝗿
• Redundancy: Active-passive vs. active-active configurations.
• Health Checks.
• Retries and Circuit Breakers: How to protect systems from cascading failures.

𝟴. 𝗖𝗗𝗡𝘀 (𝗖𝗼𝗻𝘁𝗲𝗻𝘁 𝗗𝗲𝗹𝗶𝘃𝗲𝗿𝘆 𝗡𝗲𝘁𝘄𝗼𝗿𝗸𝘀)
• Static Content Delivery: Why use a CDN, how does it work?
• Caching at the Edge: How CDNs improve latency for end users.

𝟵. 𝗔𝗣𝗜 𝗗𝗲𝘀𝗶𝗴𝗻 𝗮𝗻𝗱 𝗥𝗮𝘁𝗲 𝗠𝗮𝗻𝗮𝗴𝗲𝗺𝗲𝗻𝘁
• REST vs. GraphQL: Difference and practical use-cases for each.
• Pagination and Filtering: Strategies for efficiently fetching data.
• API Versioning: Best practices for evolving APIs.
• Throttle Requests: Why rate limiting is essential, algorithms like token bucket, leaky bucket.

𝟭𝟬. 𝗦𝗲𝗮𝗿𝗰𝗵 𝗦𝘆𝘀𝘁𝗲𝗺𝘀
• Indexing: Building and maintaining indexes for fast search.
• Full-Text Search Engines: ElasticSearch, Azure AI Search.
• Ranking and Relevance: Basic understanding of how scoring works.

𝟭𝟭. 𝗠𝗼𝗻𝗶𝘁𝗼𝗿𝗶𝗻𝗴 𝗮𝗻𝗱 𝗢𝗯𝘀𝗲𝗿𝘃𝗮𝗯𝗶𝗹𝗶𝘁𝘆 𝗮𝗻𝗱 𝗦𝗲𝗰𝘂𝗿𝗶𝘁𝘆
• Metrics Collection: Prometheus, Grafana.
• Distributed Tracing: OpenTelemetry, Sentry.
• Centralized Logging.
• Authentication and Authorization: OAuth, JWT.
• Encryption: Data in transit vs. data at rest.

If you master these 11 areas, you’ll be ready for most system design interviews thrown at you.

Job Interview – Top 25 Trickiest Question

1. Tell me about yourself…

It’s a simple question, but the answer has a million possibilities. What does the hiring manager actually want to know about you? You know you shouldn’t get too personal, but you should also show your personality a bit. Here’s how to handle this first tricky question:

  • Avoid starting with personal information
  • Highlight why your skills make you the right person for the job
  • Talk about your interests related to the role
  • Think of your answer as a story
  • Keep it short and sweet
  • Confirm if you’ve answered the question

Possible answer to “Tell me about yourself…”

“I graduated from [Insert University] with a degree in [Insert Degree]. I started my career at [Insert Company] but moved on after about a year to try something new. That’s where I really found an interest in event production. I’ve managed over 15 successful events, the most recent one being 2 months ago at my current company. I have experience finding speakers, writing talk tracks, and creating presentations for different types of in-person and virtual events. I really enjoy event planning because I work well under pressure and thrive in fast-paced environments—which is what interests me about this role. I also feel the industry is very rewarding. I am currently looking for a career change as I would like room for growth and to work for a larger, more established tech-forward firm like your company.”

2. Why are you leaving your job?

While many job seekers in this position are likely leaving their job because they’re unhappy, you know that you can’t just say, “I don’t like my job/manager/company leadership.” It’s important to still be truthful, but respectful. Here’s a few tips:

  • Be honest
  • Be professional
  • Be succinct, but thoughtful
  • Look ahead

The most important item is to turn your answer into what you are looking for. What are you no longer getting in your current role that you would like in this role?

Possible answer to “Why are you leaving your job?”

I’ve been at my current job for some time and don’t feel there is any room for growth. I’m really interested in working on XYZ and developing my skills further. I’m ready for the next step in my career and believe this new role will be what I am looking for.

3. Why were you let go from your previous employer?

Whether you were laid off or fired from your previous role, this can be an uncomfortable or tricky interview question to answer. However, you are not the only person who has ever been let go from a job. An understanding hiring manager knows that lay offs are not your fault, and some people just don’t mesh with certain office cultures or managers. Here’s a few steps you can take to answer this question:

  • Come to terms with your circumstances
  • Be honest about what happened
  • Talk about what you’ve learned as a result
  • Don’t bad mouth your previous employer

Possible answer to “Why were you let go?”

“Ultimately, my work style did not mesh well with their culture. I am often used to working regular hours, while their culture would often have people working later into the night. As a result, I couldn’t meet their expectations. I did learn, however, that I can work harder than I ever thought I could, and I appreciate the bond I built with my team when we worked late together. Based on what I understand about this role, I think I should be able to meet or exceed expectations.”

4. Why do you have a gap in employment?

Regardless of your reasoning, having a gap in employment on your resume is completely normal. However, you may be asked for an explanation in your interview. Whether you were laid off, taking a sabbatical, caring for family, or attending school, a simple answer will probably suffice. Be honest about your situation, and explain how you are prepared to transition back into work.

Possible answer to “Why do you have a gap in employment?”

“After my last role at [Company], I felt burned out. It was important to me to take a sabbatical to reconnect with myself and discover how I can prevent getting burned out in my next job. During my time off, I’ve stayed up-to-date on industry news and trends, and I’m confident I can return and hit the ground running.”

5. Why did you change career paths?

While this feels like it could be difficult to answer, don’t be overwhelmed! An honest answer about your career change is all the hiring manager is looking for. Here’s how to handle it:

  • Explain what led you to this decision
  • Show your passion for this new career
  • Highlight your transferable skills

Possible answer to “Why did you change career paths?”

“After years working in sales, I was no longer getting enjoyment out of the process of closing deals. After some soul-searching, I realized that I want to make an impact in a more meaningful way. Transitioning into Human Resources, I still get to do the thing I love most: build relationships and work directly with people. But instead of closing a sale, I get to help someone build their dream career. I’m so excited to use all of my sales skills and techniques in a more meaningful way.”

6. What motivates you?

This question might grind you to a halt—have you ever even thought about what motivates you to succeed in your work? Before you panic, consider these two things:

  • What do you like most about your work?
  • What kind of impact do you hope to make?

Possible answer to “What motivates you?”

“I really enjoy seeing the big picture and working to organize how we will achieve those big goals. When I know the end result, I know I am working toward something that impacts so many people beyond me, and it motivates me to work harder. Additionally, I love to plan and organize, diving into each detail that will get us across the finish line. That’s why I’m so excited about this project management opportunity.”

7. How many tennis balls can you fit into a pickup truck?

This is one example of many, many off-the-wall questions that have been asked over the years. You may not get this question specifically, but it’s important to know that this isn’t really a math problem with one right answer. The hiring manager is really looking to see how you think through a problem. Here’s how to answer this type of tricky interview question:

  • Don’t give up immediately and say it’s impossible
  • Say your entire thought process out loud
  • Ask for a pen and paper (or a calculator), if you need it

Possible answer to “How many tennis balls can you fit into a pickup truck?”

“Well, let’s say we’re using cannisters of tennis balls that come in threes. Perhaps nine cannisters make up about one cubic foot. May I use a calculator for this? In a pickup truck bed, there might be about 50 cubic feet. So, let’s say about 1,350 tennis balls, probably more if you include the front seat.”

8. What can you contribute to this company?

Beyond revenue or specific tasks of your role, the hiring manager is looking to learn a little bit more about your personality; how can you add to the culture of the company and also build on its success? To answer this effectively, you should:

  • Have a deep understanding of the company through your research
  • Connect your experience to the job description
  • Use data to back up your answer
  • Leverage your soft skills and personality to stand out

Possible answer to “What can you contribute to this company?”

“In my last role, I successfully transformed our team’s operations with the implementation of [technology product]. As a result, our productivity jumped by almost 40%. I am always looking for ways to improve and learn something new. I hope I could bring that energy to your team and find new improvements for us. I’m excited to learn more from you about how I can improve in this role.”

9. Why do you want to work at this company?

This is where your in-depth company research is going to shine! When you are conducting research on the company, this should be one of the main questions you are looking to answer. As a result, ask yourself the following during your research:

  • What do I like most about this business?
  • Do the company’s mission and values speak to me?
  • How does this company make an impact, and what do I like about that?
  • What can you learn about the company or team’s operations that excites you?

Possible answer to “Why do you want to work at this company?”

“I always knew that I wanted to work in this industry because I truly believe that the impact you make is so important. However, your mission goes beyond that and really speaks to how I view my job. I can also see that your organization is growing rapidly, and I am always looking to step up into new challenges. It seems like your department in particular leads the charge on that growth, and I’m excited to be at the forefront of that change.”

10. Why do you want this job?

Similar to the above question, your answer should be specific to the company and the role. What stands out about this job from all the other jobs out there? Ask yourself:

  • What skills do I bring to the table?
  • How can I grow in this role?
  • How can I make an impact?
  • What is most exciting about this job?
  • What do I want most in my next job?
  • What do I like about the company?

Possible answer to “Why do you want this job?”

“Aside from really identifying with [Company]’s mission, I know that I can add to that impact in this role. As I am leaving my current job, I am looking for a strong team to collaborate with and learn from, as well as opportunities for growth. I’m excited by the idea that I could eventually [XYZ] in this role, and I know that my skills in [ABC] can help us increase our impact as a team.”

11. Why should we hire you?

Now, that’s an intimidating (and tricky) interview question. However, this is your straightforward opportunity to sell yourself to this hiring manager! In your answer, you should:

  • Sell your experience
  • Explain how you can add to the culture
  • Show why you are better than other candidates

Possible answer to “Why should we hire you?”

“I know from my experience in this field that it can be challenging to find someone who can step up to the plate by both planning and executing in this type of role. However, my experience has allowed me to grow in both of these areas, with over 7 years dedicated to understanding the execution of this responsibility. After I understood the machinations of that, my role at [previous company] allowed me to plan each step of the process from A to Z. With that experience, I feel confident that I can not only manage this team effectively, but increase collaboration and retention through my management techniques. I have such a niche background with my experience at [company] doing [skill] that I think it really makes me a unique fit for this role. I certainly hope that I could make a positive impact on your team.”

12. Sell me this pen.

If you’re in a sales career, you may be put on the spot to sell something in front of you. What the hiring manager is looking for is:

  • Your ability to sell under pressure
  • Your confidence and charisma
  • How you can make a connection with a buyer

Possible answer to “Sell me this pen.”

“My mom always used to tell me that I shouldn’t worry about the little things in life. And, of course, she was right. It might be an extreme example, but those little things that you stress over: what to wear, which route to take to work, what to write with—they add up. And when you hardly have time for lunch, the last thing you need to worry about is whether you brought a pen into the meeting. The unique features of this pen, from the pocket clip to the small magnet, to the unique ink used, are all designed so that you don’t have to worry about pens. It may not be something you think about often until you don’t have one. In using these pens myself, I keep one in every room, and I even got my mom to switch to them as well—she keeps one on the refrigerator to write down reminders. And after a year, it still hasn’t run out of ink! With our deal happening today, you can get a second pack for free when you order the first. Can I put you down for your first order?”

13. What is your work style?

It’s not too difficult to imagine what your interviewer is trying to learn with this question. Not only do they want to understand how you might fit into this role and their team, but they also might want to know how you prefer to work so they can better manage you. To answer this, think about:

  • How you like to collaborate with team members
  • How and when you do your best work
  • How you organize and structure your day

Possible answer to “What is your work style?

“I find that my organizational tools are the key to my success. I usually manage all of my tasks within a platform where I can track everything, and make sure I don’t let anything drop. I’ve worked on teams where we all use these platforms, which can be a fun way to collaborate! In terms of getting work done, I like to set aside blocks of time for focused work and research, and I love coming together with my team for brainstorming sessions. I always find new, exciting ideas in that type of environment. Lastly, I appreciate when context is given for the projects I’m working on, as I find that it helps me prioritize my tasks.”

14. What is your management style?

This can be difficult to put your finger on; one manager never matches another in their style. However, you can articulate your answer by asking yourself questions like:

  • How do you set goals for direct reports?
  • How do you motivate employees?
  • How do you explain projects or tasks?
  • How do you provide learning and growth opportunities?

Possible answer to “What is your management style?”

“I tend to find that my management style is dictated by the individual. I have to learn how they communicate, how they internalize information, and what specifically motivates them. One employee may learn by diving in and doing something themselves, while another may learn by watching. One thing I am always sure to do is to give full context into the tasks I’m asking them to handle. I find this helps with motivation and to show their overall impact. Lastly, I think trust is critical to a functional manager/employee relationship. Without trusting each other, we’re not likely to succeed together.”

15. What is your greatest strength?

Employers will ask “what is your greatest strength?” to identify whether your abilities match up with their requirements. They want to know if you feel confident, you can do the job, and you’d be a great addition to the team. As a result, this question is a great opportunity to show a hiring manager why you’d be a great fit! Answer by:

  • Identifying your top strengths
  • Focusing on how those strengths are great for the role
  • Backing this up with a story

Possible answer to “What is your greatest strength?”

“I think one of my best qualities is clear communication. When I interact with clients at [Company], I am often dealing with people in [industry] who don’t necessarily speak the same language that we do. However, I can identify their struggle and articulate it back to them in a way that we both understand. I can also explain solutions in simple ways that make it easy for them to grasp the value of our products. For example, I once worked with a client who struggled with [XYZ]. They were not an expert in that and had trouble properly explaining their problem. However, I was able to pick up on it and show them exactly how we could help. They are still a client today, and throughout the years, I’ve acted as a valuable resource to them, which continues to show my expertise in the field. I think this type of communication skill is exactly what you’re looking for in this role, which is why I’m so excited about this opportunity.”

16. What is your biggest weakness?

If you’ve never been asked this question during a job interview before, you’re likely in the minority of working professionals. For many, discussing your weaknesses during a job interview is an expectation and something you should prepare for in the lead-up to the interview. While nobody likes to admit what makes them less than perfect, you can use this opportunity to your advantage and present yourself as the best possible person for the position! To answer this, try the following:

  • Don’t try a cop out answer like “I’m a perfectionist”
  • Be honest about your flaws
  • Avoid technical answers like “I’m not great with Excel”
  • Say what you’re doing to improve

Possible answer to “What is your biggest weakness?”

“I sometimes struggle with multitasking because I have difficulty staying organized. However, I’ve been working to improve upon this by writing down what tasks I need to accomplish, and then prioritizing them by their deadline.”

17. How do you handle conflict in the workplace?

Since conflict can come into play in a variety of situations at work, employers love to ask questions about conflict in the workplace to evaluate your interpersonal skills. In your answer, you should be able to explain:

  • How you process your thoughts
  • A willingness to listen without judgment
  • Your ability to remain professional

Possible answer to “How do you handle conflict in the workplace?”

“When I have a disagreement with someone, my first step is to listen to their argument. I always go in with the idea that I might be missing something that is key to understanding. If I still disagree with them, I try to think about all of my reasons for my perspective and present them as clearly as possible. At the end of the day, I always remain professional and defer to leadership to make a decision.”

18. What is your dream job?

The key to preparing a successful answer to this question is to understand why an employer is asking it in the first place. They want to understand your skillset, level of motivation, values, and overall interests. The hiring manager doesn’t need you to repeat their job description back to them, but they’d like to see that you are the right fit. When in doubt, you can use your goals and ambitions as a starting place, and connect why this job will help you get there.

Possible answer to “What is your dream job?”

“While I’m not sure what my end goal in my career is, I do know that I want to be at the forefront of the design field. When I think about what I need to do to get there, I know I have a lot to learn before I could be a leader in this space. But this opportunity at [Company] can certainly get me closer to that dream. As leaders in design technology, I know that I will gain so much out of this experience, and I’m really motivated to make a meaningful contribution here.”

19. Where do you see yourself in 5 years?

A similarly agonizing question to the previous one; you’re not a fortune teller after all. Ultimately, this is an opportunity to think about the next steps in your career. One or two turns down the road, where would you like to be? It’s also okay if you’re not sure—simply express that this role could be an important step to helping you figure out what it is that you want for your career.

Possible answer to “Where do you see yourself in 5 years?”

“What I’m most excited about in this role is learning from your team, as I am coming from a smaller operation. In five years, I would hope that I have a better understanding of how a large team operates and that I can contribute effectively in multiple areas. I would also love to grow into a leadership role and help others grow as well.”

20. Are you interviewing with any other companies?

This is a tricky interview question to answer because you have to strike the right balance. On the one hand, you want the hiring manager to know that you are talented and in-demand, but you also don’t want to seem blasé about this opportunity. You also don’t want to show your hand if you’re not in any other interview processes. Overall, honestly is the best policy here, but you don’t have to give it all away.

Possible answer to “Are you interviewing with any other companies?”

“I’m currently in other interview processes, all at different stages. Each role has something unique to offer, but I love the growth opportunities that exist here, so I’m excited about continuing the process.”

Or…

“I’ve just started my job search process, so I’ve had a couple of introductory conversations so far. I’m most intrigued by this role and the opportunity to make such a powerful impact.”

Hint: If you have other offers on the table, let them know. This will get the employer to move faster and make a more competitive offer.

21. When can you start?

This question is less tricky than it sounds! What is important to remember is that you should be realistic about what you can promise. Additionally, you want to consider showing respect to your current employer, which the hiring manager will appreciate. If you don’t have any commitments to a current employer, let them know so you can start sooner!

Possible answer to “When can you start?”

“I want to be as respectful as possible to my current employer, and I have a lot of responsibility in my current role, so, if possible, I would like to give four weeks’ notice upon my acceptance of an offer. Would a start date on [date] work for you?”

22. Do you have any questions for us?

Remember, an interview is a two-way street. However, many job seekers do not take advantage of this opportunity. If you don’t ask questions when given the chance, this tells the employer that you:

  • Are not interested in the role
  • Lack curiosity about the company and role
  • Didn’t do your research
  • Don’t know what you want from this role

Possible question topics you can ask about:

When you are presented the opportunity to ask questions, consider asking about:

  • The role
  • The team
  • The culture
  • Opportunities for growth
  • The company

23. What are your salary expectations?

At the heart of any interview process comes the time to discuss the big ‘S’ word: salary. Whether you’re confident in your negotiation skills or not, beginning the conversation with a hiring manager about salary expectations can be a tricky one. To answer this, it is good to start by:

  • Doing your own market research
  • Asking what the salary range is for this position
  • Asking about the total compensation package
  • Giving a salary range you are comfortable in (keep in mind, they may present an offer at the lower end, so be strategic about the range you give)

Possible answer to “What are your salary expectations?”

“Taking my experience level into account, as well as your budget for this role and what I understand to be the market value of this position, I would be looking for a salary range between $80,000 and $85,000. I would also love to learn about other elements of your compensation and benefits packages to take the full compensation value into consideration.”

24. What is your current salary?

This question, while not illegal everywhere in the U.S., is illegal in several states across the country because it perpetuates a cycle of underpaying workers rather than paying for the skillset required to do the job. Regardless of whether this question is legal in your state, you do not have to answer it. If it is asked, deflect by giving your target salary range.

Possible answer to “What is your current salary?”

“Well, my target salary for my next position is around $60,000 to $65,000.”

25. Are you planning to have children?

Keep in mind, this question, along with any questions about race, ethnicity, citizenship status, country of origin, religion, gender, sexual orientation, or marital/family status are illegal. However, the question still gets asked—and often (many times by accident). So, how do you handle this? Try to keep your answer brief and vague, before steering back into the job and company.

Possible answer to “Are you planning to have children?”

“I really try to keep my work and personal life separate. But, I would love to discuss more about this role. Can you tell me what I can expect to accomplish in the first six months here?”

S3 as database

Prompt

Quais são as estratégias de armazenamento de banco de dados no S3?

O Amazon S3 (Simple Storage Service) é uma solução de armazenamento baseada em nuvem amplamente utilizada para armazenar grandes volumes de dados. Embora não seja um banco de dados tradicional, ele pode ser combinado com outras tecnologias para criar estratégias eficientes de armazenamento. Abaixo estão algumas estratégias comuns para usar o S3 como parte de uma arquitetura de banco de dados:

1. Data Lake

O S3 é frequentemente usado como Data Lake para armazenar grandes volumes de dados estruturados, semi-estruturados e não estruturados.

  • Estratégia: Os dados são organizados em buckets e pastas lógicas (prefixos).
    • Por exemplo: s3://data-lake/finance/2024/11/transactions.csv
  • Formatos de Arquivo: Arquivos otimizados para análise, como Parquet, Avro ou ORC, são usados para maior eficiência.
  • Integração: Ferramentas como AWS Glue, Amazon Athena ou Amazon Redshift Spectrum podem consultar diretamente os dados no S3.

2. Backup e Arquivamento

O S3 é frequentemente usado para backup ou arquivamento de dados de bancos de dados relacionais e NoSQL.

  • Estratégia:
    • Backups regulares: Exportar snapshots ou dumps de bancos de dados como MySQL, PostgreSQL ou MongoDB para o S3.
    • Armazenamento em camadas: Usar classes de armazenamento do S3, como S3 Standard-IA ou S3 Glacier, para otimizar custos.
  • Automação: Serviços como AWS Backup podem automatizar o processo.

3. Bancos de Dados Estilo “Data Lakehouse”

Para arquiteturas que combinam Data Lake com funcionalidades de banco de dados.

  • Estratégia:
    • Combinar S3 com AWS Lake Formation ou Delta Lake para oferecer controle de transações e versionamento.
    • Ferramentas como Snowflake e Databricks também permitem consulta de dados diretamente no S3 com otimização.
  • Benefícios: Garante escalabilidade e suporte a cargas de trabalho analíticas.

4. Sharding e Partitioning

Armazenar grandes conjuntos de dados em S3 pode ser feito usando sharding e partitioning para acesso eficiente.

  • Estratégia:
    • Divida os dados em partições baseadas em colunas como data, localização ou IDs.
    • Exemplo: s3://my-db/users/2024/11/28/partition-01.csv
  • Automação: Processos ETL podem criar e gerenciar as partições.

5. Data Tiering (Armazenamento Hierárquico)

Usado para mover dados frios (menos acessados) de bancos de dados ativos para o S3.

  • Estratégia:
    • Dados ativos permanecem em um banco de dados transacional, enquanto dados históricos são migrados para o S3.
    • Usar ferramentas como AWS Database Migration Service (DMS) para mover dados gradualmente.

6. Data Warehousing

Para integrações com data warehouses baseados em S3.

  • Estratégia:
    • Use o Amazon Redshift Spectrum para consultar dados diretamente no S3.
    • Combine dados armazenados no Redshift com dados no S3 para uma solução híbrida.

7. Armazenamento de Logs

O S3 é ideal para armazenar grandes volumes de logs.

  • Estratégia:
    • Logs de aplicações e sistemas são enviados diretamente para buckets do S3.
    • Integração com AWS CloudTrail ou Amazon CloudWatch Logs para governança e análise.

8. Sincronização com Bancos de Dados Não Relacionais

Alguns bancos de dados NoSQL suportam integração direta com o S3.

  • Exemplo:
    • DynamoDB suporta backups no S3.
    • ElasticSearch/Opensearch pode enviar snapshots para o S3.

9. Aplicações de Machine Learning

Os dados no S3 são frequentemente usados como base para treinamento e inferência de modelos.

  • Estratégia:
    • Dados armazenados no S3 são processados por serviços como SageMaker ou frameworks externos para ML/IA.
    • Pré-processamento e armazenamento em formato Parquet ou JSON.

Boas Práticas:

  1. Versionamento: Ative o versionamento no bucket para proteger contra exclusões acidentais.
  2. Criptografia: Use criptografia (SSE-S3 ou SSE-KMS) para proteger dados sensíveis.
  3. Políticas de Acesso: Configure políticas IAM e S3 Bucket Policies para restringir acesso.
  4. Replicação: Use replicação entre regiões (CRR) para maior disponibilidade.
  5. Monitoramento: Ative logs de acesso e AWS CloudTrail para auditoria.

Essas estratégias ajudam a aproveitar a escalabilidade e a economia do S3 para diversos casos de uso em banco de dados.

Clean Code – 7 princípios

Conheça as 7 principais princípios do Clean Code

No livro do Uncle Bob foram listadas algumas boas práticas para se obter um código limpo. Os principais são:

1 – Nomes são muito importantes

A definição de nome é essencial para o bom entendimento de um código. Aqui, não importa o tipo de nome, seja ele:

  • Variável;
  • Função;
  • Parâmetro;
  • Classe;
  • Método.

Ao definir um nome, é preciso ter em mente 2 pontos principais:

  1. Ele deve ser preciso e passar logo de cara sua ideia central. Ou seja, deve ir direto ao ponto;
  2. Não se deve ter medo de nomes grandes. Se a sua função ou parâmetro precisa de um nome extenso para demonstrar o que realmente representa, é o que deve ser feito.

2 – Regra do escoteiro

Há um princípio do escotismo que diz que, uma vez que você sai da área em que está acampando, você deve deixá-la mais limpa do que quando a encontrou.

Trazendo a regra para o mundo da programação, a regra significa deixar o código mais limpo do que estava antes de mexer nele.

3 – Seja o verdadeiro autor do código

O ser humano é acostumado a pensar de forma narrativa , portanto, o código funciona da mesma forma. Logo, ele é uma história e, como os programadores são seus autores, precisam se preocupar na maneira com que ela será contada.

Em resumo, para estruturar um código limpo, é necessário criar funções simples, claras e pequenas. Existem 2 regras para criar a narrativa via código:

  1. As funções precisam ser pequenas;
  2. Elas têm de ser ainda menores.

Não confunda com os termos “nome” e “função”. Como dissemos no primeiro princípio, nomes grandes não são um problema. Já as funções precisam ser as menores possíveis.

4 – DRY (Don’t Repeat Yourself)

Esse princípio pode ser traduzido como “não repita a si mesmo”. Essa expressão foi descrita pela primeira vez em um livro chamado The Pragmatic Programmer e se aplica a diversas áreas de desenvolvimento, como:

  • Banco de Dados;
  • Testes;
  • Documentação;
  • Codificação.

O DRY diz que cada pedaço do conhecimento de um sistema deve ter uma representação única e ser totalmente livre de ambiguidades. Em outras palavras, define que não pode existir duas partes do programa que desempenhem a mesma função.

5 – Comente apenas o necessário

Esse princípio afirma que comentários podem ser feitos, porém, se forem realmente necessários. Segundo Uncle Bob, os comentários mentem. E isso tem uma explicação lógica.

O que ocorre é que, enquanto os códigos são constantemente modificados, os comentários não. Eles são esquecidos e, portanto, deixam de retratar a funcionalidade real dos códigos.

Logo, se for para comentar, que seja somente o necessário e que seja revisado juntamente com o código que o acompanha.

6 – Tratamento de erros

Tem uma frase do autor Michael Feathers, muito conhecido na área de desenvolvimento, que diz que as coisas podem dar errado, mas, quando isso ocorre, os programadores são os responsáveis por garantir que o código continuará fazendo o que precisa.

Ou seja: saber tratar as exceções de forma correta é um grande e importante passo para um programador em desenvolvimento.

7 – Testes limpos

Testar, na área de programação, é uma etapa muito importante. Afinal, um código só é considerado limpo após ser validado através de testes – que também devem ser limpos.

Por isso, ele deve seguir algumas regras, como:

  • Fast: O teste deve ser rápido, permitindo que seja realizado várias vezes e a todo momento;
  • Independent: Ele deve ser independente, a fim de evitar que cause efeito cascata quando da ocorrência de uma falha – o que dificulta a análise dos problemas;
  • Repeatable: Deve permitir a repetição do teste diversas vezes e em ambientes diferentes;
  • Self-Validation: Os testes bem escritos retornam com as respostas true ou false, justamente para que a falha não seja subjetiva;
  • Timely: Os testes devem seguir à risca o critério de pontualidade. Além disso, o ideal é que sejam escritos antes do próprio código, pois evita que ele fique complexo demais para ser testado.

 

O Clean Code é um conceito que veio para ficar. Afinal, seus princípios solucionam com eficácia um dos principais problemas que grande parte dos projetos de sistemas enfrentam: a manutenção.

Clean Code

Clean Code

No universo da programação, frequentemente nos deparamos com o termo: Clean Code ou Código Limpo.

Mas o que exatamente é um “código limpo”? Quais características são necessárias para obtê-lo?

Escrever um código limpo significa escrever códigos de um jeito que conseguimos entendê-lo sem complicação.

Isso não apenas simplifica a manipulação do código, mas também facilita a colaboração entre o time. No fim das contas, todo desenvolvimento e manutenção do sistema também se torna mais fácil.

De acordo com “Uncle Bob”, em seu livro “Código Limpo: Habilidades Práticas do Software Ágil”, existem algumas boas práticas fundamentais para alcançar a clareza do código.

Vamos conhecê-las, a seguir:

Utilizar os princípios SOLID:

O Clean Code e os princípios SOLID compartilham o objetivo de melhorar a qualidade do software, tornando-o legível, organizado, extensível e fácil de manter.

Neste episódio do #HipstersPontoTube sobre Clean Code e Solid, o host Paulo Silveira bate um papo com o Alberto Sousa para discutir se é necessário seguir essas práticas à risca para desenvolver um bom projeto.

Possuir nomes significativos

Nomes descritivos ajudam a entender a finalidade de uma parte do código sem a necessidade de comentários explicativos.

Para ilustrar, considere o código a seguir:

public static double conv(double tC) {
    double tF = (tC * 9 / 5) + 32;
    return tF;
}

Temos que nos esforçar para entender o que o código acima faz. Podemos melhorar o entendimento apenas adicionando nomes significativos para as variáveis e para o método:

public static double converterCelsiusParaFahrenheit(double temperaturaCelsius) {
    double temperaturaFahrenheit = (temperaturaCelsius * 9 / 5) + 32;
    return temperaturaFahrenheit;
}

Agora, fica claro qual é o propósito do código, sem a necessidade de se lembrar de fórmulas ou realizar pesquisas adicionais. Isso economiza tempo e evita confusões desnecessárias.

Priorizar o uso de funções pequenas

Escrever métodos ou funções pequenas e focadas em uma única tarefa é fundamental para manter o código claro e seguir o princípio da responsabilidade única (SRP).

Para ilustrar, considere o código a seguir:

public class Main {

    public static void main(String[] args) {
        int[] numeros = {1, 2, 3, 4, 5};

        int soma = 0;
        for (int numero : numeros) {
            soma += numero;
        }

        double media = (double) soma / numeros.length;

        if (media > 3) {
            System.out.println("A média é maior que 3");
        } else {
            System.out.println("A média é menor ou igual a 3");
        }
    }
}

Apesar do uso de nomes descritivos, a legibilidade poderia ser melhorada dividindo as tarefas em funções distintas, cada uma com sua descrição. Por exemplo:

public class Main {

    public static void main(String[] args) {
        int[] numeros = {1, 2, 3, 4, 5};

        int soma = calcularSoma(numeros);
        double media = calcularMedia(numeros);
        verificarEMostrarResultado(media);
    }

    public static int calcularSoma(int[] numeros) {
        int soma = 0;
        for (int numero : numeros) {
            soma += numero;
        }
        return soma;
    }

    public static double calcularMedia(int[] numeros) {
        return (double) calcularSoma(numeros) / numeros.length;
    }

    public static void verificarEMostrarResultado(double media) {
        if (media > 3) {
            System.out.println("A média é maior que 3");
        } else {
            System.out.println("A média é menor ou igual a 3");
        }
    }
}

Embora o código tenha ficado maior, ganhamos em legibilidade e segmentação. Qualquer pessoa que precise alterar a maneira como a média é exibida à pessoa usuária, só precisa modificar o método verificarEMostrarResultado.

Isso demonstra como funções pequenas podem facilitar a manutenção e a compreensão do código.

Evitar comentários desnecessários

O código deve ser autoexplicativo, com nomes significativos e estrutura lógica clara. Comentários excessivos podem tornar o código poluído e difícil de manter.

Para ilustrar, considere o código a seguir:

public class R {
    private double w;
    private double h;

    // Método para calcular a área
    public double calc() {
        return w * h;
    }
}

Os nomes curtos para as variáveis dificultam o entendimento, fazendo necessário o uso de comentários no nosso código, deixando o nosso código sujo. Então, podemos resolver isso adicionando nomes descritivos e removendo os comentários:

public class Retangulo {
    private double largura;
    private double altura;

    public Retangulo(double largura, double altura) {
        this.largura = largura;
        this.altura = altura;
    }

    public double calcularArea() {
        return largura * altura;
    }
}

Pronto, perceba como facilitou o entendimento. Qualquer pessoa desenvolvedora que ler este código consegue assimilar o que cada parte faz.

Evitar complexidade

A complexidade desnecessária pode aumentar a chance de erros e tornar o código difícil de manter. Um exemplo de código complexo para fazer algo simples, como somar dois números, seria:

public void soma() {
        Scanner scanner = new Scanner(System.in);

        System.out.print("Digite o primeiro número: ");
        String num1String = scanner.nextLine();

        System.out.print("Digite o segundo número: ");
        String num2String = scanner.nextLine();

        boolean validInput = false;
        double num1 = 0;
        double num2 = 0;

        while (!validInput) {
            num1 = Double.parseDouble(num1String);
            num2 = Double.parseDouble(num2String);
            validInput = true;
        }

        double soma = num1 + num2;

        System.out.println("A soma dos números é: " + soma);

        scanner.close();
    }

Repare que é feita uma verificação da entrada, para só depois convertê-la em double.

Poderíamos simplesmente considerar que é esperado que o usuário digite um double e fazer o tratamento de exceções relacionado a isso:

import java.util.InputMismatchException;
import java.util.Scanner;

public class SimpleSumWithErrorHandling {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        try {
            System.out.print("Digite o primeiro número: ");
            double num1 = scanner.nextDouble();

            System.out.print("Digite o segundo número: ");
            double num2 = scanner.nextDouble();

            double soma = num1 + num2;

            System.out.println("A soma dos números é: " + soma);
        } catch (InputMismatchException e) {
            System.out.println("Erro: Por favor, digite números válidos.");
        } finally {
            scanner.close();
        }
    }
}

Executamos a lógica desejada de forma rápida e fácil de compreender. Imagine que precisamos somar agora 3 variáveis, ao invés de duas.

É mais fácil modificar o segundo código do que o primeiro. O segundo código é um exemplo de código limpo.

Fazer o mínimo de argumentos

Funções e métodos devem ter o mínimo possível de argumentos. Isso melhora a legibilidade e a facilidade de uso.

No exemplo a seguir, note como cadastrar uma pessoa colaboradora é complexo ao passar muitos parâmetros:

public static void cadastrarFuncionario(String nome, int idade, String cargo, double salario, String endereco, String cidade, String cep, String telefone, String email) {
        // Lógica de cadastro do funcionário aqui...

    }

Ao chamar esse método, é difícil entender qual parâmetro utilizar em que lugar, podendo confundi-los, por exemplo.

Uma boa alternativa seria criar uma classe para representar o funcionário, outra para o endereço, e mais uma para o contato. Assim, faremos a divisão:

public static void cadastrarFuncionario(Funcionario funcionario, Endereco endereco, Contato contato){
}

Dessa forma, conseguimos agrupar as informações para que seja possível usar menos argumentos. Essa é uma boa prática

Evitar código com repetição

A repetição torna o código difícil de manter, pois quando há mudanças necessárias elas precisam ser aplicadas em múltiplos lugares.

Então, extraia código repetido em funções ou métodos para promover a reutilização e a manutenção eficiente.

Um exemplo disso:

public static void main(String[] args) {
        int numero1 = 5;
        int numero2 = 7;

        // Cálculo do fatorial para o primeiro número
        int resultado1 = 1;
        for (int i = 1; i <= numero1; i++) {
            resultado1 *= i;
        }
        System.out.println("Fatorial de " + numero1 + ": " + resultado1);

        // Cálculo do fatorial para o segundo número
        int resultado2 = 1;
        for (int i = 1; i <= numero2; i++) {
            resultado2 *= i;
        }
        System.out.println("Fatorial de " + numero2 + ": " + resultado2);
    }

Como calculamos o fatorial mais de uma vez, extraímos o código para uma função, evitando repetições de cálculo no código:

public static void main(String[] args) {
        int numero1 = 5;
        int numero2 = 7;

        // Cálculo e impressão do fatorial para o primeiro número
        calcularEImprimirFatorial(numero1);

        // Cálculo e impressão do fatorial para o segundo número
        calcularEImprimirFatorial(numero2);
    }

    // Função para calcular e imprimir o fatorial de um número
    public static void calcularEImprimirFatorial(int numero) {
        int resultado = 1;
        for (int i = 1; i <= numero; i++) {
            resultado *= i;
        }
        System.out.println("Fatorial de " + numero + ": " + resultado);
    }

Dessa forma, se precisarmos calcular novamente o fatorial de outro número, não precisaremos repetir código. Basta chamar a função de calcular fatorial novamente. Isso facilita o desenvolvimento.

Vantagens de deixar o código limpo

Ao implementar cada um desses princípios e práticas, você não apenas irá melhorar a qualidade do seu código, mas também facilitará a compreensão, a manutenção e a colaboração no desenvolvimento de software. Tudo isso que discutimos aqui resume uma frase dita por Martin Fowler:

“Qualquer tolo pode escrever código que um computador pode entender. Bons programadores escrevem código que humanos podem entender”.

A programação não se trata apenas de fazer a máquina funcionar, mas também de criar soluções que sejam compreensíveis e colaborativas.

SOLID

O que é SOLID?

O acrônimo SOLID representa os cinco princípios que facilitam o processo de desenvolvimento — o que facilita a manutenção e a expansão do software.

Estes princípios são fundamentais na programação orientada a objetos e podem ser aplicados em qualquer linguagem que adote este paradigma.

Os 5 princípios são:

  • S — Single Responsibility Principle (Princípio da responsabilidade única)
  • O — Open-Closed Principle (Princípio Aberto-Fechado)
  • L — Liskov Substitution Principle (Princípio da substituição de Liskov)
  • I — Interface Segregation Principle (Princípio da Segregação da Interface)
  • D — Dependency Inversion Principle (Princípio da inversão da dependência)

Origem dos princípios SOLID

O primeiro indício dos princípios SOLID apareceu em 1995, no artigo “The principles of OoD” de Robert C Martin, também conhecido como “Uncle Bob”.

Nos anos seguintes, Robert se dedicou a escrever mais sobre o tema, consolidando esses princípios de forma categórica.

E, em 2002, lançou o livro “Agile Software Development, Principles, Patterns, and Practices” que reúne diversos artigos sobre o tema.

Agora que você já sabe o significado da sigla e a origem dos princípios, é importante dar um passo para trás para compreender o paradigma da Programação Orientada a Objetos (POO). Afinal de contas, como você já sabe, através da POO é possível aplicar os princípios SOLID.

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

Para entender o princípio da responsabilidade única, vamos pensar no desenvolvimento de um gerenciador de tarefas. Vamos começar com o seguinte código:

public class GerenciadorTarefas {

    public String conectarAPI(){
        //...
    }
    public void criarTarefa(){
        //...
    }

    public void atualizarTarefa(){
        //...
    }

    public void removerTarefa(){
        //...
    }

    public void enviarNotificacao(){
        //...
    }

    public void produzirRelatorio(){
        //...
    }

    public void enviarRelatorio(){
        //...
    }

}

Problemática

Tente enumerar todas as funções que a classe GerenciadorTarefas tem. Ela é responsável por lidar com todas as operações das tarefas em si e também está consumindo uma API, enviando notificações para pessoas usuárias e ainda gerando relatórios da aplicação.

Pense na Orientação a Objetos. Um objeto gerenciador de tarefas deveria enviar e-mails e gerar relatórios? Não! Um gerenciador de tarefas gerencia as tarefas, não e-mails ou relatórios.

Solução

Para resolver esse problema vamos criar classes diferentes, cada uma representando uma função.

Nossa classe GerenciadorTarefas terá apenas o código relacionando a operação com tarefas. Outras operações estarão em outras classes. E cada classe será responsável por uma parte diferente da aplicação.

Assim, teremos a classe GerenciadorTarefas refatorada:

public class GerenciadorTarefas {

    public void criarTarefa(){
        //...
    }

    public void atualizarTarefa(){
        //...
    }

    public void removerTarefa(){
        //...
    }
}

Assim, vamos criar uma classe para consumir uma API externa, outra classe para enviar notificações e uma última classe para lidar com os relatórios.

public class ConectorAPI {

    public String conectarAPI() {
        //...
    }

}
public class Notificador {

    public void enviarNotificacao() {
        //...
    }

} 
public class GeradorRelatorio {
    public void produzirRelatorio(){
        //...
    }

    public void enviarRelatorio(){
        //...
    }

}

Talvez você se pergunte se as classes não são pequenas demais. Nesse caso, não estão. Cada classe reflete exatamente a responsabilidade que ela tem.

Se precisarmos adicionar algum método, por exemplo, relacionado ao consumo da API, vamos saber exatamente em qual parte do código devemos ir. Ou seja, fica muito mais fácil alterar o que for preciso.

Definição do Princípio da Responsabilidade Única

Em resumo, o princípio da responsabilidade única diz que: “Cada classe deve ter um, e somente um, motivo para mudar.”

Se uma classe tem várias responsabilidades, mudar um requisito do projeto pode trazer várias razões para modificar a classe. Por isso, as classes devem ter responsabilidades únicas.

Esse princípio pode ser estendido para os métodos que criamos também. Quanto mais tarefas um método executa, mais difícil é testá-lo e garantir que o programa está em ordem.

Uma dica para aplicar o princípio na prática é tentar nomear suas classes ou métodos com tudo que eles são capazes de fazer.

Se o nome está gigante, como GerenciadorTarefasEmailsRelatorios, temos um sinal de que o código pode ser refatorado.

Vantagens de aplicar o Princípio da Responsabilidade Única

Existem vários benefícios ao aplicar esse princípio, principalmente:

  • Facilidade para fazer manutenções
  • Reusabilidade das classes
  • Facilidade para realizar testes
  • Simplificação da legibilidade do código

Princípio Aberto-Fechado (O – Open Closed Principle)

Para entender o Princípio Aberto-Fechado (a letra O da sigla), vamos pensar que estamos trabalhando no sistema de uma clínica médica.

Nessa clínica, existe uma classe que trata das solicitações de exames. Inicialmente, o único exame possível é o exame de sangue. Por isso, temos o código:

public class AprovaExame {
    public void aprovarSolicitacaoExame(Exame exame){
        if(verificaCondicoesExameSangue(exame))
            System.out.println("Exame aprovado!");
    }
    public boolean verificaCondicoesExameSangue(){
        //....
    }
}

Agora, precisamos incluir uma nova funcionalidade ao sistema: a clínica vai começar a fazer exames de Raio-X. Como incluir isso no nosso código?

Uma alternativa seria verificar qual o tipo de exame está sendo feito para poder aprová-lo:

public class AprovaExame {
    public void aprovarSolicitacaoExame(Exame exame){
        if(exame.tipo == SANGUE){
            if(verificaCondicoesExameSangue(exame))
                System.out.println("Exame sanguíneo aprovado!");
        } else if(exame.tipo == RAIOX) {
            if (verificaCondicoesRaioX(exame))
                System.out.println("Raio X aprovado!");
        }

    }
    private boolean verificaCondicoesExameSangue(){
        //....
    }

    private boolean verificaCondicoesRaioX(){
        //....
    }
}

Problemática

A princípio parece tudo certo, não é mesmo? Nosso código executa normalmente e conseguimos adicionar a funcionalidade corretamente.

Mas, e se além de raio-x, a clínica passasse a fazer também ultrassons? Seguindo a lógica, iríamos adicionar mais um if no código e mais um método para olhar condições específicas do exame.

Essa definitivamente não é uma boa estratégia. Cada vez que incluir uma função, a classe (e o projeto como um todo) vai ficar mais complexa.

Por isso, é necessário uma estratégia para adicionar mais recursos ao projeto, sem modificar e bagunçar a classe original.

Solução

Nesse cenário, o projeto compreende vários tipos de aprovação de exames. Assim, podemos criar uma classe ou uma interface que representa uma aprovação de forma genérica.

A cada tipo de exame fornecido pela clínica, é possível criar novos tipos de aprovação, mais específicos, que irão implementar a interface. Assim, podemos ter o código:

public interface AprovaExame{
    void aprovarSolicitacaoExame(Exame exame);
    boolean verificaCondicoesExame(Exame exame);

}
public class AprovaExameSangue implements AprovaExame{
    @Override
    public void aprovarSolicitacaoExame(Exame exame){
            if(verificaCondicoesExame(exame))
                System.out.println("Exame sanguíneo aprovado!");

    }
    @Override
    boolean verificaCondicoesExame(Exame exame){
        //....
    }
}
public class AprovaRaioX implements AprovaExame{
    @Override
    public void aprovarSolicitacaoExame(Exame exame){
        if(verificaCondicoesExame(exame))
            System.out.println("Raio-X aprovado!");

    }
    @Override
    boolean verificaCondicoesExame(Exame exame){
        //....
    }
}

Agora, como a interface representa a aprovação de um exame, para incluir mais um recurso ou mais um tipo de exame, basta criar uma nova classe que implementa a interface AprovaExame. Essa classe vai representar como o novo exame é aprovado.

Repare que sempre será possível implementar a interface AprovaExame ao adicionarmos recursos. Essa interface, no entanto, não muda. Estamos estendendo-a, mas não alterando.

Definição do Princípio Aberto-Fechado

Assim, é possível definir o Princípio Aberto-Fechado como: “entidades de software (como classes e métodos) devem estar abertas para extensão, mas fechadas para modificação”.

Ou seja, se uma classe está aberta para modificação, quanto mais recursos adicionarmos, mais complexa ela vai ficar.

O ideal é adaptar o código não para alterar a classe, mas para estendê-la. Em geral, isso é feito quando abstraímos um código para uma interface.

Aplicando o Open-Closed, é possível deixar o nosso código semelhante ao mundo real, praticando de maneira sólida a orientação a objetos.

Pense em um caminhão: toda a sua implementação, como motor, bateria e cabine é fechada para modificação.

Vantagens de aplicar o Princípio Aberto-Fechado

Ao aplicar esse princípio, é possível tornar o projeto muito mais flexível. Adicionar novas funcionalidades torna-se uma tarefa mais fácil.

Além disso, os códigos ficam mais simples de ler. Com isso tudo, o risco de introduzir bugs diminui de forma significativa.

Além disso, esse princípio nos faz caminhar diretamente para a aplicação de alguns padrões de projeto, como o Strategy.

Assim, alinhamos várias boas práticas de desenvolvimento. O resultado disso é um código cada vez mais limpo e organizado.

Princípio de Substituição de Liskov (L – Liskov Substitution Principle)

Para entender o Princípio de Substituição de Liskov (a letra L da sigla), vamos pensar no seguinte cenário: o desenvolvimento de um sistema de uma faculdade.

Dentro do sistema, há uma classe-mãe Estudante, que representa um estudante de graduação, e a filha dela, EstudantePosGraduacao, tendo o seguinte código:

public class Estudante {
    String nome;

    public Estudante(String nome) {
        this.nome = nome;
    }

    public void estudar() {
        System.out.println(nome + " está estudando.");
    }
}
public class EstudanteDePosGraduacao extends Estudante {

    @Override
    public void estudar() {
        System.out.println(nome + " está estudando e pesquisando.");
    }
}

Para adicionar a funcionalidade entregarTCC() ao sistema, basta colocar esse método na classe Estudante O código fica assim:

class Estudante {
    String nome;

    public Estudante(String nome) {
        this.nome = nome;
    }

    public void estudar() {
        System.out.println(nome + " está estudando.");
    }

    public void entregarTCC(){
    //…
    }

}

Problemática

Você provavelmente já percebeu algo errado no código. Normalmente, estudantes de pós-graduação não entregam TCCs.

Só que a classe EstudanteDePosGraduacao é filha de Estudante, e portanto, deve apresentar todos os comportamentos dela.

Uma alternativa seria sobrescrever o método entregarTCC na classe EstudanteDePosGraduacao lançando uma exceção.

No entanto, continuaria sendo problemático: a classe EstudanteDePosGraduacao ainda não teria os comportamentos iguais aos de Estudante.

O ideal é que, nos lugares que estiver a classe Estudante, seja possível usar uma classe EstudanteDePosGraduacao, já que, pela herança, um estudante de pós-graduação é um estudante.

Solução

A solução para este problema é modificar a nossa modelagem. Podemos criar uma nova classe EstudanteDeGraduacao, que também herdará de Estudante. Essa classe terá o método entregarTCC:

public class EstudanteDeGraduacao extends Estudante {
    public void estudar() {
        System.out.println(nome + " está estudando na graduação.");
    }

    public void entregarTCC() {
    //…
    }
}

Repare que, dessa forma, nossas classes representam melhor o mundo real. Não estamos forçando uma classe a fazer algo que ela originalmente não faz.

Além disso, se precisarmos utilizar uma instância de Estudante, podemos passar, sem medo, uma instância de EstudanteDeGraduacao ou de EstudanteDePosGraduacao.

Afinal de contas, essas classes conseguem executar todas as funções de Estudante — mesmo tendo funções mais específicas.

Definição do Princípio da Substituição de Liskov

Quem propôs o Princípio da Substituição de Liskov, de maneira formal e matemática, foi Bárbara Loskov.

No entanto, Robert Martin deu uma definição mais simples para ele: “Classes derivadas (ou classes-filhas) devem ser capazes de substituir suas classes-base (ou classes-mães)”.

Ou seja, uma classe-filha deve ser capaz de executar tudo que sua classe-mãe faz. Esse princípio se conecta com o polimorfismo e reforça esse pilar da POO.

É importante notar também que, ao entendermos esse princípio, passamos a nos atentar mais para o código: caso um método de uma classe-filha tenha um retorno muito diferente do da classe-mãe, ou lance uma exceção, por exemplo, já dá para perceber que algo está errado.

Se no seu programa você tem uma abstração que se parece com um pato, faz o som de um pato, nada como um pato, mas precisa de baterias, sua abstração está equivocada.

Vantagens de aplicar o Princípio da Substituição de Liskov

Aplicar esse princípio nos traz diversos benefícios, especialmente para ter uma modelagem mais fiel à realidade, reduzir erros inesperados no programa e simplificar a manutenção do código.

Princípio de Segregação de Interface (I – Interface Segregation Principle)

Para entender o Princípio de Segregação da Interface, imagine que estamos trabalhando com um sistema de gerenciamento de funcionários de uma empresa.

Vamos criar uma interface, conforme o código abaixo:

Interface Funcionário
public interface Funcionario {

    public BigDecimal salario();
    public BigDecimal gerarComissao();

}

Repare que criamos a interface para estabelecer um “contrato” com as pessoas que são funcionárias dessa empresa. N

esse contexto, o código a seguir descreve duas classes que fazem referências a duas profissões nessa empresa: Vendedor e Recepcionista.

Ambas usam a interface Funcionario e, portanto, devem implementar os métodos salario() e gerarComissao().

Classe Vendedor
import java.math.BigDecimal;

public class Vendedor implements Funcionario {

    @Override
    public BigDecimal salario() {
    }

    @Override
    public BigDecimal gerarComissao() {
    }

}
Classe Recepcionista
import java.math.BigDecimal;

public class Recepcionista implements Funcionario{

    @Override
    public BigDecimal salario() {
    }

    @Override
    public BigDecimal gerarComissao() {
    }

}

Problemática

Analisando o código acima, faz sentido uma pessoa que possui o cargo de vendedora ou recepcionista ter salárioSim! Afinal, todos nós temos boletos para pagar.

Seguindo esta mesma linha, faz sentido uma pessoa com cargo de vendedor ou recepcionista ter comissãoNão!.

Para uma pessoa que tem o cargo de vendedora, faz sentido. Mas para a pessoa que tem o cargo de recepcionista, não faz sentido.

Ou seja, a classe Recepcionista foi forçada a implementar um método que não faz sentido para ela. Embora ela seja funcionária dessa empresa, esse cargo não recebe comissão.

Portanto, podemos perceber que este problema foi gerado por temos uma interface genérica.

Solução

Para resolver isso, é possível criar Interfaces específicas. Ao invés de ter uma única interface Funcionário, podemos ter duas: Funcionario e Comissionavel.

Interface Funcionário
import java.math.BigDecimal;

public interface Funcionario {
    public BigDecimal salario();
}

Repare que mantemos a interface Funcionario, mas retiramos o método gerarComissao() a qual é específico de algumas pessoas, para adicioná-lo em uma nova interface FuncionarioComissionavel:

Interface Comissionável
import java.math.BigDecimal;

public interface Comissionavel{
    public BigDecimal gerarComissao();
}

Agora, a pessoa que possui o direito de ter comissão irá implementar a interface Comissionavel, um exemplo disso é a classe Vendedor:

Vendedor
import java.math.BigDecimal;

public class Vendedor implements Funcionario, Comissionavel{

    @Override
    public BigDecimal salario() {
    }

    @Override
    public BigDecimal gerarComissao() {
    }

}

Agora, a classe Recepcionista pode implementar a interface Funcionario sem ter a obrigação de criar o método gerarComissao():

Recepcionista
import java.math.BigDecimal;

public class Recepcionista implements Funcionario{
    @Override
    public BigDecimal salario() {
    }
}

Definição do Princípio da Segregação da Interface

Conforme analisamos o código acima, podemos perceber que:

Devemos criar interfaces específicas ao invés de termos uma única interface genérica.

E é justamente isto que Princípio da Segregação da Interface diz: “Uma classe não deve ser forçada a implementar interfaces e métodos que não serão utilizados”.

Vantagens de aplicar o Princípio da Segregação da Interface

Seguir o Princípio da Segregação da Interface ajuda a promover a coesão e a flexibilidade em nossos sistemas, tornando-os fáceis de manter e estender.

Princípio da Inversão de Dependência (D – Dependency Inversion Principle)

Para compreender o Princípio da Inversão de Dependência (letra O da sigla) imagine que estamos trabalhando em uma startup de e-commerce e precisamos desenvolver o sistema de gerenciamento de pedidos.

Sem conhecer o Princípio da Inversão de Dependência, é bem provável que vamos desenvolver uma classe PedidoService semelhante ao código abaixo:

Classe PedidoService
public class PedidoService {
    private PedidoRepository repository;

    public PedidoService() {
        this.repository = new PedidoRepository();
    }

    public void processarPedido(Pedido pedido) {
        // Lógica de processamento do pedido
        repository.salvarPedido(pedido);
    }
}

Problemática

Aparentemente, o código parece estar certo. No entanto, se um dia precisar alterar o armazenamento deste pedido para um outro lugar (por exemplo, uma API externa), vai precisar de mais de uma classe para resolver o problema.

Afinal, a classe PedidoService está diretamente acoplada à implementação concreta da classe PedidoRepository.

Solução

Para resolver este problema, podemos criar uma interface para a classe de acesso ao banco de dados e injetá-la na classe `PedidoService´.

Dessa forma, nós estamos dependendo de abstrações e não de implementações concretas.

Interface PedidoRepository
public interface PedidoRepository {
    void salvarPedido(Pedido pedido);
}
Classe PedidoService
public class PedidoService {
    private PedidoRepository repository;

    public PedidoService(PedidoRepository repository) {
        this.repository = repository;
    }

    public void processarPedido(Pedido pedido) {
        // Lógica de processamento do pedido
        repository.salvarPedido(pedido);
    }
}

Deste modo, conseguimos fazer com que a classe de alto nível (PedidoService) seja independente dos detalhes de implementação da classe de baixo nível (PedidoRepository).

Definição do Princípio da Inversão de Dependência

Princípio da Inversão de Dependência diz: “dependa de abstrações e não de implementações concretas”.

Assim, é recomendado que os módulos de alto nível não dependam diretamente dos detalhes de implementação de módulos de baixo nível.

Em vez disso, eles devem depender de abstrações ou interfaces que definem contratos de funcionamento. Isso promove maior flexibilidade e facilita a manutenção do sistema.

Vantagens de aplicar o Princípio da Inversão de Dependência

A adesão ao Princípio de Inversão de Dependência promove a flexibilidade e a extensibilidade dos nossos sistemas.

Isso faz com que seja mais fácil fazer testes de unidade e construir códigos mais robustos e duradouros.