Introduction
In this post, I present a set of tools, habits, and rules that I have incorporated into my career, drawing insights from both my own mistakes and those of others.
Given the subjective nature of some of the topics discussed, it’s important not to perceive this post as industry standards. Each individual may have a different perspective. However, based on my experiences, I feel equipped to offer valuable advice in this area.
This post deviates from the technical focus of most of my writings. So, grab a cup of coffee, relax, and feel free to read—perhaps even accompanied by some background music.
Your tasks are your bread and butter
Many engineers aspire to go above and beyond for their company, and this is a quality that managers should actively promote.
I am referring to tasks that are not explicitly assigned and may not be documented in task tracking platforms, conveyed verbally by managers, or communicated through any other medium. Examples include:
- Creating Kubernetes YAMLs and Helm charts to facilitate simple local environment deployment, thereby enhancing the productivity of the entire R&D staff.
- Recording lectures on interesting topics.
- Refactoring code to improve readability significantly, even if not specifically requested.
- Writing a post for the company’s tech blog.
- Learning a pertinent technology that the company may not currently utilize.
There are numerous other ways to contribute to your company that may not be officially documented in Jira or other platforms.
However, it’s essential to recognize that unless you approach your manager and gain their agreement to include the task in Sprint planning, such tasks are considered extra-mile efforts and are not part of the critical path. Consequently, prioritization becomes crucial.
This is not to dissuade you from going the extra mile, but rather to caution against allowing it to hinder your primary objective—delivery. The ultimate measure of success lies in delivering results, as that is the core reason for our professional endeavors.
Every task is meticulously evaluated, understanding that its completion may impact the prioritization of other tasks in future sprints. Failing to deliver on one task necessitates a reshuffling of priorities for other tasks.
So, how can you still go the extra mile?
- Make it an official task: Engage with your manager, persuading them that the task is important enough to be designated as an official assignment.
- Work on it during downtime: When faced with a lull or waiting for input from others, utilize this time to make progress on these extra-mile tasks.
- Invest personal time: If you’re passionate about a particular initiative, consider dedicating some of your free time to work on it.
- Ensure a clean plate: If you’ve completed all your assigned tasks, approach your manager and inquire if they have urgent assignments or if you can focus on the extra-mile task.
Know your business
In any organizational structure, the product department plays a pivotal role, serving as the driving force behind product development by formulating requirements and shaping the company’s product lines. It is within this department that the core focus lies.
However, failure to comprehend the motivations behind the products can significantly impede your ability to fulfill assigned tasks and provide viable alternatives when needed. In essence, a thorough understanding of the products is crucial for effective collaboration and the generation of valuable contributions.
Arrogance vs Confidence
You can effectively teach others when you embrace humility and recognize that every individual, irrespective of their professional background, possesses insights that can contribute to your learning.
Confidence becomes evident when you express your ideas and articulate your thoughts; contributing meaningfully requires an active engagement with your surroundings. However, it’s crucial to acknowledge that confidence and humility are interconnected. Crossing the boundary from confidence to arrogance can detrimentally impact your conduct and prove counterproductive for the company.
Certainly, recognizing when this line is crossed can be challenging. Here are a few self-monitoring tips to assist you:
Empower Less Experienced Developers: Encourage junior developers to share their opinions during code reviews. If someone finds a particular part challenging to understand, focus on demonstrating why it is true rather than dismissing it as wrong. Collaboratively explore ways to enhance the code.
Respectful Critique: Constructive Feedback: Avoid making criticisms without a comprehensive examination and understanding. If you encounter a technological decision made by another team that gives rise to concerns:
Invest time in reading their documentation and conducting thorough research.
Frame insightful questions.
Approach discussions with a respectful demeanor.
If the decision has an impact on your team, propose a meeting with the other team to collectively explore and find a solution.
Testing Is Everyone’s Responsibility: Regardless of seniority, every developer should prioritize testing their code. It’s surprising how some developers consider writing tests as beneath their responsibilities. Remember, ensuring the quality of your code is integral, irrespective of your seniority level.
Listen to people!
Acknowledge that you don’t know everything, and even junior developers can offer valuable insights.
When in charge of design, be open to suggestions from other developers implementing it—they have a right to contribute, and your attentive listening fosters a collaborative environment.
Use shortcuts
Developers under my guidance are well aware that I have a strong aversion to excessive hand-to-mouse interactions. Here are a few examples that illustrate my stance:
- Renaming variables by manually navigating to each occurrence.
- Moving code to separate functions through manual copying and pasting.
- Searching for method names using Ctrl+F.
- Starting with the type name instead of relying on IntelliJ IDEA’s auto-completion.
It’s concerning to observe individuals hindering their workflow by not optimizing the use of the code editor.
For those who may not be familiar with shortcuts, the initial recommendation after reading this post is to consult your IDE’s shortcuts page, print it out, and place it in proximity to your workspace.
Here are some common IntelliJ IDEA shortcuts for macOS:
- Mark Clause: ⌘ W (pressing it multiple times adds more code clauses)
- Copy Marked Code to Variable Assignments: ⌘⌥V
- Change Variable/Function/Class Name: Shift + F6
- Reformat Lines: ⌥ ⌘ L
- Move Cursor to Errors in the File: F2
- Wrap in Try-Catch: Mark the code and press ⌥ ⌘ T, then choose Try Catch
- Search Specific Line: ⌘ G
- Select All: ⌘ A
Given that JetBrains’ various IDEs, especially IntelliJ IDEA, are widely used, here are a few links that might be helpful:
Productivity
What really bugs me is when a developer skips shortcuts and is dead set on not fixing things that slow us down. Spending a day fixing a local issue might mean we take two steps forward instead of one, and that’s totally worth it.
Back in my full-stack days, our Jenkins-controlled deployment dance involved both Java and JS code. We had to go through the drill of compiling, waiting for JUnit tests, and only then getting a fully functional system with the client code.
Imagine just working on the client code and having to wait for the whole system to compile before checking if your changes even worked. Ugh!
Expressing the need for optimization, I conveyed, “This is undeniably unnecessary; we are expending an undue amount of time here.” Subsequent to thorough discussions with various stakeholders, I successfully obtained approval to independently deploy client code. The outcome was a significant reduction in deployment times for client tasks, decreasing from 20 minutes to a mere 2.
The moral of the story: if something’s holding you back, whether it’s a bottleneck or a clunky process, tackle it. Always remember the golden rule of fixing things that mess with your efficiency.
Be an Engineer rather than a coder
Follow S.O.L.I.D principles
Proposed by the esteemed Robert Martin, also known as Uncle Bob, these constitute a collection of design principles within object-oriented programming, advocating for modularity, flexibility, and maintainability. Elaborating on each principle is beyond the scope of this text, as numerous online posts and articles delve into comprehensive explanations and examples. I suggest exploring the original insights from the individual who introduced these principles, which brings us to the next point.
Code Right, Code Clean
In his notable work, “Clean Code,” Robert Martin provides valuable insights into the optimal development of software, emphasizing its extensibility. Importantly, the book not only outlines the “why” and “how” but also offers a perspective that enlightens one on the characteristics of well-crafted software.
I strongly encourage reading this book prior to delving into technology-specific materials. The principles and guidelines elucidated in “Clean Code” have withstood the test of time, ensuring their continued relevance for years to come.
Stability Patterns
Your code may function correctly under ideal circumstances, assuming ample memory, no I/O stress, and consistently operational connected services. However, in reality, numerous unpredictable events can occur at any given time.
It is crucial to proactively ensure that your software possesses the capability to autonomously recover. Consider the following example, where two services communicate synchronously with each other.
In many scenarios, we can anticipate that the userApi will request data from the analyticsService, leading to a subsequent query to the analytics database.
However, in certain situations, an extensive computation on the database end may occur, consuming significant CPU and memory resources. During such instances, users may experience prolonged latencies, leading to frustration. The typical human response in such cases is to repeatedly hit the refresh button (F5). If measures aren’t in place to prevent multiple and duplicate requests from being processed simultaneously, the database may face instability. This, in turn, triggers more refresh attempts, potentially leading to a database crash.
What transpired here? In terms of correctness, no wrongdoing occurred—perhaps an extra 10 seconds of patience could have resolved the issue. However, user behavior is beyond our control; we only have influence over the services handling their requests and the client side initiating them.
So, what can be done? Options include limiting the number of queries a user can make, caching queries to prevent concurrent processing, and automatically scaling out the database. The key takeaway is to not assume that both the system and users will exhibit patience and resilience.
In this context, I strongly endorse “Release It!: Design and Deploy Production-Ready Software” by Michael Nygard. This exceptional book delves into strategies for managing instability, exploring patterns such as Fail-Fast, circuit-breaker, extending timeouts, and proper resource management, among others.
Understand concepts / not just technologies
Do you believe Kafka pioneered the concept of publish-subscribe? Were ClickHouse and Druid the initial analytical databases to utilize columnar formats? Is it accurate to assert that only Cassandra incorporates replication, sharding, LSM trees, and Bloom filters, or that discussions on the CAP model are exclusive to MongoDB developers?
It is essential to recognize that, on a theoretical level, certain concepts endure over time or, at the very least, persist for an extended duration.
My recommendation is that, when acquiring knowledge of a technology, one should comprehend the underlying concepts rather than focusing solely on its highly technical intricacies. You may be surprised by how many other technologies of the same family employ the same fundamental concepts.
In this regard, I also suggest reading books that delve into the theory behind a practice, as opposed to merely instructing on a specific technology. For instance, as a Data Engineer, one of my preferred books is “Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems, 1st Edition” by Martin Kleppmann.
This book thoroughly explores topics such as replication, sharding, LSM trees, B-trees, OLAP, OLTP, consistency, MapReduce, streaming applications, and much more.
Design before code
The planning phase is crucial before diving into coding, providing a comprehensive understanding of the broader picture and anticipating potential challenges. A well-documented planning and design phase should include the following elements:
Connection Points to Other Services:
- Define how elements are interconnected, considering factors such as communication between servers, synchronous or asynchronous communication, and the nature of data transfer.
- Specify ports and DNS names.
Tests:
- Outline the tests to be performed, excluding TDD (test-driven development) but focusing on tests planned post-development.
- Outline the tests to be performed, excluding TDD (test-driven development) but focusing on tests planned post-development.
- Security:
- Address security concerns, including verification of tenant isolation, access levels, and adherence to OWASP top 10 security vulnerabilities.
Readability & Extensibility:
- Evaluate code readability and extensibility based on S.O.L.I.D principles and guidelines from “Clean Code.”
- Evaluate code readability and extensibility based on S.O.L.I.D principles and guidelines from “Clean Code.”
Performance:
- Conduct performance testing and assess the reliability of tests in predicting production outcomes.
- Conduct performance testing and assess the reliability of tests in predicting production outcomes.
Logs/Monitoring/Alerts:
- Define how to detect issues and establish alerts to notify the cloud team of problems.
- Identify potential risks and assess the impact on other system components.|
Resources:
- Specify the cloud resources required for the feature.
- Specify the cloud resources required for the feature.
Backward Compatibility:
- Evaluate whether the backend can work with old data.
- Assess whether the client side can seamlessly adapt to new changes without modifications.
Forward Compatibility:
- Consider a mobile application’s ability to handle unfamiliar data fields from the latest server’s version, ensuring it remains functional for users who do not upgrade immediately.
- Consider a mobile application’s ability to handle unfamiliar data fields from the latest server’s version, ensuring it remains functional for users who do not upgrade immediately.
Costs:
- Account for the costs associated with specific cloud services and features.
- Emphasize the importance of optimizing resource usage to avoid unnecessary expenses.
- For instance, BigQuery is a commendable product; however, charges are determined by the processed data volume. To ensure optimal utilization, implement suitable Extract, Transform, Load (ETL) strategies to minimize data processing volume. No one would appreciate a request taking a second to be answered if it incurs a cost of $5 and triggers 10,000 times daily.
Use your time efficiently – what to do when you are stuck
I’ve found myself in countless situations where I was waiting for a teammate, especially when permissions and keys for accessing cloud resources were required. However, I don’t just sit idle during such instances; I refer to this proactive approach as “getting out of limbo.” Here are steps to keep the wheels turning:
Communication:
- Clearly communicate your need to your coworker, expressing the urgency. If they are unavailable, escalate the matter to the manager.
- Clearly communicate your need to your coworker, expressing the urgency. If they are unavailable, escalate the matter to the manager.
Find Workarounds:
- Explore alternative solutions such as using mocks, like the localstack platform to simulate AWS services.
- Work with dummy data to continue progress.
Promote Other Tasks:
- If the manager is unresponsive and there are other tasks in the queue, tackle them. Opt for smaller tasks to minimize deep context-switching.
- If the manager is unresponsive and there are other tasks in the queue, tackle them. Opt for smaller tasks to minimize deep context-switching.
“Extra-Mile” Tasks:
- Use the time productively to initiate projects or ideas you’d like to present and push forward.
- Use the time productively to initiate projects or ideas you’d like to present and push forward.
Focus on POCs and Documentation:
- Concentrate on Proof of Concepts (POCs) and documentation, particularly if you plan to present new ideas to your managers.
- Concentrate on Proof of Concepts (POCs) and documentation, particularly if you plan to present new ideas to your managers.
Learn:
- Invest time in learning through course videos and reading technological books to enhance your skill set.
By adopting these steps, you can proactively navigate through periods of waiting, ensuring that your time is utilized effectively and contributing to both personal and team development.
How to learn legacy code
When dealing with code that is not your own and requires modification or extension, there are essential questions to consider:
Where to Set Key Breakpoints for Debugging:
- Identify strategic locations for breakpoints to facilitate effective debugging.
- Identify strategic locations for breakpoints to facilitate effective debugging.
Availability of Tests for Viewing and Debugging:
- Check for existing tests as they provide a more focused and efficient means of debugging compared to running the entire system.
- Check for existing tests as they provide a more focused and efficient means of debugging compared to running the entire system.
Bottom-Up Approach:
- Explore the possibility of working from the bottom up, especially if the design allows for it. This can save the need to run every service in the system.
Example:
- In the querying layer, can you work with mock data? If not, consider opening a ticket to address this and discuss its importance with your manager. Running the entire data pipeline may be unnecessary for extending the querying layer.
- In the processing layer, assess if you can work on a single phase separately or if you need to involve previous phases.
- Consider running integration tests to verify your code before checking it on the macro level (i.e., all services together).
Availability of Resources (Video or Wiki):
- Check for any available video tutorials or documentation in wikis that can provide additional insights into working with the specific codebase.
By addressing these questions, you can approach unfamiliar code systematically, enhancing your ability to modify or extend it effectively.
Know when to take a break
Allowing yourself breaks is crucial for maintaining both your mental well-being and your capacity to deliver value. Without taking time for other interests and cultivating curiosity in different subjects, you risk burnout, which can significantly impact both your professional and personal life.
Whether it involves spending time with friends and family, delving into books, or exploring a new hobby, the key is to avoid becoming a “dev-bot” and to actively engage in life beyond work.