Showing posts with label engineering. Show all posts
Showing posts with label engineering. Show all posts

Wednesday, October 30, 2024

Portable code enables a smooth flow to production for the product

What is portability? The application or platform you are currently developing needs to work on your machine but also in the production environment where all the users are using it. The measure of Portability is the ability of the application to behave predictably in all environments. 

In this article, I talk about portable configuration and using code branching effectively with your team when developing software products. By adding some DORA metrics to your reporting, it's easy to quantify the effect portability has on your overall quality


why do I need to do this? 


The reliability of the software increases when it works in production without modification, so this effort starts when developing on your local machine. When developing an application there is going to be a need to build the app locally and deploy it to your production environment for the users to use the app. There could be testing, staging, and other environments along the way. Enabling some good practices for the configuration of the app makes developing and testing a lot easier and reduces the stress of deployment.


Keys to success:

  • A new person should be able to pull, run tests, start app with just .local.env, and can hook into secrets in a vault when in dev, staging and production environments
  • Keep environment config versions and use the environment to use a specific file.


how do I do this?


The application code shouldn’t change, the configuration of the code is what changes. This configuration is unique to each environment (local, dev, staging, production). Reduce the complexity of moving to production removes barriers and increases velocity

  • Collaborative development with PRs
    • The sooner the smallest architecture changes are established, the easier the validation in the build process. The maintainability of the codebase is fundamental to portable software
  • Configuration of the System. 
    • By deploying to the integration environment and testing the system, you can gain confidence that the system will work the same way in production
    • use env files and keep secrets safe in cloud storage
  • Configuration of the product
    • Use Feature Flagging to limit access to incomplete functionality

Branches of Code


How to integrate my work with the code from other developer teams? Use Trunk-based development for collaboration and velocity. This process allows integration of PRs and rapid deployment of the outcomes.


What is the trunk? it is the main branch and should match the deployed branch in the production environment. When changes are in the main branch, expect those changes to work immediately in staging and production environments.

  • Ensures smaller changes
    • features that aren’t ‘ready’, that don’t fulfill the full intention of the feature and don’t fully solve the user problem don’t need to be in a separate branch,
  • Use feature flags to only allow users that have the alpha, beta or GA access, but remove them and tie them to the license source of truth for access and usage stats to understand the impact (and usefulness) to the user
    • sometimes an alpha version is required to show progress and demoing the functionality through the alpha-beta-GA cycle 
  • Prevents over-engineering through smaller changes
  • Favours rebasing changes on the main branch instead of large merges. This keeps the history cleaner on the main branch and makes rollbacks much easier if needed. 

Feature Branches

  • building a feature in a separate branch sounds like a good idea, but incurs a lot of maintenance effort when keeping the branch up to date
  • frequent updating and merging
  • merge conflicts

Release branches

  • When a deployment to production is complete, the semver of the release is known. It can ease the transition to trunk-based development to create a Release branch to handle any hotfixes made to the production system between releaseses. This allows small changes in prod so users aren’t blocked by using the product while the large feature branches are merged and validated. 
    • the same change is applied to the main branch with a test so the main branch has the changes integrated already for the next deploy

More on specific git commands here 

https://jseller.blogspot.com/2020/03/source-control-saves-time-and-complexity.html


Automation

Use automation to validate this portability and test the observability at the same time.

  • Use a certified CI/CD for package and dependency validation

Use pipelines in bitbucket or github to automatically deploy your main branch to a development server, for larger production deployments there is Harness and other tools that can deploy to many nodes to reduce the complexity of setting up many production instances.

  • Run tests appropriate to the environment

    1. local tests and mock data for local development
    2. Integration tests and synthetic data for staging
    3. Production smoke tests to validate

Use an automated test to add a quality step to your deployment scripts. The deployment is finished when it is validated, not just deployedValidate your observability when validating the deployment. This is essential to certify the system is up to date for deprecation or security fixes


Metrics to measure?


Good portability helps velocity and collaboration, but how does it do that? Use DORA metrics to understand how the portability is affecting deployments and rework

  • Change Failure Rate
  • Deployment frequency

https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance


Conclusion


No matter how careful you are about the portability of the app; things are going to happen when many people are working on the application. 

Adding quality through Portable patterns like code branching and automation will go a long way to ironing out the issues before deploying to production



Also see:


https://jseller.blogspot.com/2019/01/feature-definition-with-quality-model.html

https://jseller.blogspot.com/2020/03/packaging-code-for-development-and.html


https://dora.dev/capabilities/trunk-based-development/


How individual contributors add value to technology teams when not managing people

Building software products is a team sport, and there are many positions to play on any team. In software teams, technology leadership positions have evolved over the years from primarily people management to a combination of personnel and technology management.  It’s difficult for one person to fulfill these distinct responsibilities successfully; as a result, successful teams have split this role into two:  Engineering management is people management, and Staff development is focused on technology production. 


The success of the team is dependent on the people involved, and the understanding of the goals they need to achieve. The Engineering manager enables an understanding of these goals, enabling teammates to play their positions more effectively. This develops the person. 


The individual contributor isn’t the coach; they are more like the team captain and they show by example how to fulfil the responsibilities of the position. This brings other developers up to a higher level of quality development. The Staff developer moves the technology forward. While the specifics can vary from team to team, at their core, they must fulfill the fundamentals of: 

  • Technical excellence, by example
  • Predictability of delivery for stakeholders 
  • Ensuring Architecture quality through collaboration on design and review 


Technical Excellence, by example


A staff developer must show technical excellence by example and how to move development forward with quality and velocity. This effort is not just building a feature, but engineering the feature so that it has the quality needed for the product to be successful in the market. 

Starting with the code and showing guidance and practices for maintainability and portability with pull requests that follow a declared set of quality attributes that guide quality code. What is quality code? Code that follows common conventions that the group has decided on. This is formatting, linting and type safety so the code in the product is something everyone can recognize. Remember that the code isn’t the developer’s code, it is company property and must be maintained responsibly so that any developer can work on it. Working through pull requests by making sure the collaborators can review and push the code to production in a common way. There will be unknowns to figure out, but it’s necessary sometimes to run into the dark room and light it up together.


https://jseller.blogspot.com/2020/03/packaging-code-for-development-and.html

https://jseller.blogspot.com/2023/06/maintainable-and-portable-codebases.html



Staff developer Improves predictability by reducing scope. 


Understanding the context to help build features for the user that are valuable. To move the product forward, understanding the impact of the decisions helps the predictability of the process and communication during design implementation

The smaller the better and output an implementation Epic to understand the definition of done for this enhancement. Work that implementation to production.  Use feature flags to enable this functionality to the user when releasing to the production environment

https://jseller.blogspot.com/2019/01/software-releases-are-lot-more-than-code.html


Ensures architecture quality and collaboration on the definition of that quality


Operationally the Staff developer ensures a high level of adherence to the quality attributes of the system. The functionality, does it have some understanding of the problem we are solving for the user? 

Collaborate on functionality, observability, and the tools and processes that enable easy maintenance of the product in production. Adhering to known standards establishes good practices and processes. 

https://jseller.blogspot.com/2019/01/feature-definition-with-quality-model.html


Does the solution have a coherent definition of the functionality and acceptance criteria? Staff developers know they can’t build the whole thing in one change, but able to break down the solution into smaller chunks and gain agreement on the smallest definition of done that shows the value


Evolving the architecture together


To support any functionality there will be changes to the architecture. Develop a strong culture of design through a standardized process of RFC (or ADR) review in the team. 


Wrap-up


Staff development brings an awareness of the product and company’s goals to technical development. This ensures the teammates can align on the architecture and collaborate at scale to deliver a quality product. This direction, and most importantly, the collaboration with the team members, ensures that the product produced can fulfill the goals of the company’s place in the market.

Thursday, June 15, 2023

Maintainable codebases increase developer velocity by reducing complexity


Maintainability is a key Architecture Quality attribute of a well-constructed system. Maintainability is the quality attribute that helps other people on your team work on the same project and be able to add value without too much overhead to understand how it all fits together. Collaboration on defining and evolving these quality attributes will significantly affect the quality and velocity of your projects. 


Why is this important?

When you get on a project and see the code base, or just look at code you wrote 5 years ago. What is the feeling you get? Does it feel professional, or does it give you a feeling like nobody cared? 

Professional quality creates momentum in the team and quality practices become good habits. When teams are developing a product with the same codebase, teammates will bring their own opinions on best practices for code formatting and quality. Everyone has a lot of experience and curiosity, so it's good to use that knowledge to implement some simple practices to allow the team to move faster together, with more quality.

The quality outcome is teammates can make changes easily, and enhancements and fixes flow easily

What are the cures?

Code ownership 

It is the professional responsibility of the developer to produce the high-quality code that they can. This is important when developing on your own, but it is essential when developing on a team. The team owns the code and the upkeep of that quality becomes a matter of trust between team members. 


Adhere to common standards

The code has formatting and practices from the language itself. These patterns and practices become familiar and reduce the mental overhead of ‘figuring out’ the code when adding enhancements or fixing bugs.


Trust in teamwork

When you are on a pager-duty call at 3am to debug an incident. If the code is easy to read and understand, then the fix is easier to find. Do it for your teammate who is on call this week. This will also help Pull Request reviews as the code is easier to read, by our own convention. 


How do I do this effectively?


Agree on the context and goals and automate the tools to fulfill them. 


Agree on the concepts and goals, but don’t get lost in the details. If you have been in a code standards meeting, you might run into a situation where a developer is not only advocating to put a squiggly on a new line or leave it at the end. Presenting a higher-level goal of ‘commonly accepted practices’ and getting agreement on those will prevent you and your teammates from getting lost in the details. One thing I have noticed whenever implementing this policy is that the subject didn't return to the conversation. The discussion moved on to higher-level concerns

  • Don’t sweat these details, there are more valuable problems to solve. 


Automate with github actions


With these checks running locally in every commit, the same commands are run to ensure your new branch integrates with the work on the main branch. 


Starting with some simple tools that implement maintainability checks, we can automate these actions to really enable development productivity. Automating small problems helps the developer concentrate on the bigger tasks. It always helps productivity; less time and is more consistent than manual checks. 


Automate all of this by adding consistent checks in the CI and CD process

  • GitHub actions that do not allow merging until the checks pass
    • Using git for doing the tasks
    • git is an amazing piece of software engineering for source control and versioning. Also, it will run tasks or 'hooks' to run specific functionality before and/after a commit. 
  • run pre commit for everything,
  • use the same hook to run on the main branch of the repository when merging pull requests


Maintainable Python


Let us use a Python example to show what maintainable checks can be added to your developer workflow to take care of the maintainability and allow collaboration on a higher level


For this exercise, we are going to upgrade an open-source package with the tooling 


https://github.com/jseller/python-edi


For Python, we will use the following tools:

  • black, flake8, mypy, bandit, vulture
  1. Run locally with pre-commit
  2. Run the same checks in our git host actions
  3. Take a look at this PR as an example. https://github.com/jseller/python-edi/tree/upgrade
    1. For adding this functionality to a project, I wouldn't do all of this at once, but split this up and run the tools file by file before committing to the whole project

Formatting and Linting with black and flake8

  • Common code formatting and structure help team members understand each other's work. This results in a more effective PR that can be reviewed and merged easily.

For the first version, have a code formatting and lint tool. The output is much easier to review when the formatting doesn't need to be understood. Running a linter on your code will always give me more robust code and reduce complexity.


Type safety with mypy

For dynamic languages like Python and javascript, a type-checking action is really handy to increase code quality and make more robust functions and classes. Using type hints, a tool like mypy will check the validity of the variables being used to prevent TypeError exceptions. 


Security checks with bandit and vulture

Security holes and unused code can be scanned. 



Languages will naturally have conventions 


For JavaScript, we have TypeScript now that adds a lot of type safety over top of vanilla js. 

Running tools like Prettier and ES lint on legacy javascript code will bring that quality up to a higher level. 


Frameworks like React and Angular enforce conventions. Agree with that opinion and move on to focus on building value.



Code Structure

The programming language used to implement the system may use some common conventions, but it's still an application that can be decomposed into a set of features


Clean code that is common and familiar, code structured in files and directories that follow common conventions in structure and naming


Package by layer or feature?


When packaging application code for development, there is the application, and then there is all the artifacts around the application. Data files, unit tests, and different configurations are needed to build the app, and then there is the app code


package by feature

Many applications have a directory structure that reflects the implementation pattern of the system architecture and not so much the functionality it has. Directories like "Model, View, Controller" and "API, Data, Logic" or some variation of the components' structure. 


This gets complex as I would have to add and edit files to many different directories to add a feature. The new SearchCatalog feature would need some changes to a file in api, or a new file and so on with the different parts of the pattern. Not all features are implemented with the same pattern, so having a strict structure can cause issues.


Instead, try packaging by the feature itself. In a large system, this looser coupling within the files will help understand the feature as a whole


Let's make a feature called "SearchCatalog" and implement your pattern for the data models, views and logic classes that make the feature work. By better understanding the interfaces, this makes understanding the dependencies a little easier, and can also result in nice shared libraries used by many features.


Packaging and deployment

  • Using common packaging has a lot to do with Portability, but the code that does those things needs to have some maintainable characteristics
  • package app code
    • check versions of dependencies, and automate dependency security checks (GitHub and GitLab do this automatically now)
    • update when deprecation warnings are in the logs
  • deployment as code with Terraform 
    • shell scripting can get out of hand ‘to make it work’


Feature Flags

Using the pattern allows an easier addition of Feature Flags as there are fewer places to add them and all the code for a feature is in its own directory. As a result, provides a more elegant way for new code to live in the application. 


Design


Consistency in the implementation helps, but is there also maintainable design? There has always been an effort to standardize design with diagramming standards like UML and conventions like C4. The textual design has not found a decent standard that has been widely adopted. 


For easier collaboration with QA and documentation (if you have someone doing this or you are doing this yourself), link to specs that created the functionality RFCs and ADRs for design. Not sure that is worth the effort and as long as don't lose your architecture in Jira or an issue tracker


Conclusion


By establishing good habits and automating the application of those standards; a nice safety net is created that allows a flow of increased productivity. 


The team's culture is positively affected as the standards are agreed upon and evolved over time. More time is spent adding value to the product, instead of working the codebase.