Software Development Standards at Qbits: Code Quality, Security and Engineering Practices
Building software that works is only the beginning.
At Qbits, we care about what happens after the first release: how easily the code can be understood, reviewed, secured, extended, debugged, and maintained by another engineer months or even years later.
That is why we follow a set of engineering standards across our software projects.
The exact tools and implementation may differ depending on the project and technology stack, but the principles remain consistent.
Our goal is simple:
Build software that is clean, secure, predictable, testable, and maintainable.
1. Code Should Be Easy to Read
Readable code is one of the most important engineering standards we follow.
We prefer clear and predictable code over code that is unnecessarily clever or overly complex.
This means:
Using meaningful variable, function, and class names
Keeping functions focused on a clear responsibility
Avoiding unnecessary nesting and complexity
Breaking large modules into logical components
Removing duplicate and unused code
Keeping project structures consistent
Writing comments where they provide useful context
Avoiding unnecessary abstractions
A developer should be able to open a module they did not write and understand its purpose without spending hours reverse-engineering it.
Code is written once, but it may be read and modified hundreds of times.
2. Formatting Should Be Automated
Formatting should not become a major discussion during code review.
We automate it.
Depending on the project, we use tools such as:
Prettier for consistent formatting across JavaScript, TypeScript, frontend, and related projects
Biome in projects where a fast and unified formatting and linting workflow fits better
Ruff for Python formatting and linting
Other language-specific formatters where appropriate
Developers should not need to manually debate indentation, spacing, semicolons, quote styles, or other formatting preferences.
The toolchain should handle those decisions automatically.
This allows engineers to focus their time on architecture, logic, performance, security, and maintainability.
3. Linting Is Part of Development
Linting helps detect problems before they become runtime errors or reach production.
For JavaScript and TypeScript projects, we commonly use ESLint.
It helps identify issues such as:
Unused variables
Incorrect imports
Suspicious patterns
Possible bugs
Inconsistent coding practices
TypeScript-specific problems
In some projects, we also use Biome for both linting and formatting.
For Python projects, we use Ruff.
Ruff helps us detect common errors, maintain code consistency, enforce best practices, and perform static analysis efficiently.
Our general rule is straightforward:
Code should pass linting before it is considered ready for review or merge.
4. Type Safety Matters
Where supported by the technology stack, we make good use of static typing.
For TypeScript projects, we avoid unnecessary use of any and prefer clearly defined types, interfaces, and contracts.
For Python projects, type hints are encouraged for important services, functions, models, and interfaces.
Type safety helps us:
Catch problems earlier
Improve IDE support
Make APIs easier to understand
Refactor with greater confidence
Reduce assumptions between different parts of the system
Types should help developers understand the system rather than create unnecessary complexity.
5. Security Starts During Development
At Qbits, security is not treated as something added at the end of a project.
It is part of how software is designed, written, reviewed, tested, built, and deployed.
Security controls should exist across multiple layers rather than depending on a single tool or process.
Static Code Analysis with SonarQube
Where appropriate, we use SonarQube to continuously analyze source code for quality and security issues.
SonarQube helps identify areas such as:
Security vulnerabilities
Bugs
Code smells
Security hotspots
Duplicated code
Reliability concerns
Maintainability issues
Test coverage indicators
SonarQube can also be integrated into CI/CD pipelines as a quality gate.
This means code can be evaluated against defined quality and security standards before being allowed to progress further through the release process.
Automated analysis does not replace engineering judgment.
Instead, it helps engineers identify common problems early so code reviews can focus on deeper architectural, security, and business-logic concerns.
Never Hardcode Secrets
Secrets should never be committed directly to source code.
This includes:
API keys
Passwords
Access tokens
Database credentials
Private keys
AWS credentials
Third-party service credentials
Sensitive configuration should be managed through appropriate secret-management mechanisms.
Depending on the project, this may include:
AWS Secrets Manager
AWS Systems Manager Parameter Store
CI/CD secret stores
Environment-level secret management
Secrets should also be rotated when required and access should follow the principle of least privilege.
Validate Everything That Crosses a Trust Boundary
Any input entering the application from an external source should be treated as untrusted.
This includes:
API requests
Forms
Webhooks
Uploaded files
URL parameters
Third-party APIs
External integrations
User-generated content
AI-generated output used by another system
Inputs should be validated against expected structures and constraints before they are processed.
Where necessary, values should also be sanitized or encoded correctly before being stored or rendered.
Authentication Is Not Authorization
Knowing who a user is does not automatically mean they should have access to every resource.
Authentication and authorization should be treated separately.
Access control should be based on factors such as:
Roles
Permissions
Ownership
Tenant boundaries
Application rules
Authorization checks should be performed at the appropriate application layer and not depend solely on the frontend.
Protect Against Common Security Risks
Depending on the application, engineers should consider risks such as:
SQL injection
Cross-site scripting
Cross-site request forgery
Server-side request forgery
Broken access control
Insecure direct object references
Unsafe file uploads
Weak authentication flows
Improper session handling
Insecure dependency usage
Exposed secrets
Excessive permissions
Security needs to be considered at both the code level and the architecture level.
6. Container Images Must Be Scanned
For containerized workloads, securing the application source code alone is not enough.
A container image can also contain vulnerable libraries, operating-system packages, dependencies, configuration issues, or exposed secrets.
At Qbits, we use Trivy to scan container images before deployment.
Trivy helps identify:
Known vulnerabilities
Vulnerable OS packages
Vulnerable application dependencies
CVEs
Container image issues
Misconfigurations
Exposed secrets in supported scanning workflows
Where appropriate, Trivy is integrated directly into the CI/CD pipeline.
This allows container images to be checked automatically before they are promoted to production.
The goal is to identify high-risk issues before deployment rather than discovering them after an incident.
For containerized applications, our security approach therefore covers multiple layers:
Source code → dependencies → container image → deployment
7. Dependencies Are Part of the Security Surface
Adding a library or package is easy.
Maintaining it safely over time is not.
Before introducing a new dependency, engineers should consider:
Is it actively maintained?
Is it widely trusted?
Are there known vulnerabilities?
Is the package actually necessary?
Could the same requirement be solved without introducing another dependency?
How large is its dependency tree?
Will it become difficult to replace later?
We also use lock files and predictable package versions wherever appropriate.
Dependencies should be monitored and updated when security or stability issues are discovered.
Every package introduced into a system becomes part of that system's operational and security surface.
8. Git History Should Be Understandable
Version control is more useful when the history tells a clear story.
We aim to maintain clean branches, meaningful commits, and understandable pull requests.
Commit messages should describe what changed.
Instead of:
fix
Prefer:
fix: prevent duplicate order creation
Instead of:
changes
Prefer:
feat: add role-based access control
A clear Git history makes debugging, auditing, rollback, and collaboration easier.
9. Pull Requests Should Be Reviewable
Large pull requests are difficult to review properly.
Where possible, changes should be broken into logical and manageable pieces.
A good pull request should communicate:
What changed
Why it changed
How it was implemented
Any important architectural decisions
Potential risks
How it was tested
Whether infrastructure or configuration changes are required
Code review should examine more than syntax.
Reviewers should consider:
Logic
Architecture
Security
Performance
Maintainability
Error handling
Tests
Edge cases
Backward compatibility
The objective of code review is not simply to approve a change.
It is to improve the quality and reliability of the system.
10. Testing Should Reflect Risk
Not every line of code requires the same level of testing.
We prioritize testing based on the impact of failure.
Areas such as authentication, payments, permissions, financial calculations, important APIs, data processing, and critical business workflows generally require stronger testing.
Depending on the project, this may include:
Unit tests
Integration tests
API tests
End-to-end tests
Regression tests
Testing should give engineers confidence that they can modify a system without unintentionally breaking existing behaviour.
Test coverage can be useful, but percentage alone is not enough.
The quality and relevance of tests matter more than the number of tests.
11. Error Handling Should Be Intentional
Applications will eventually encounter failure.
Networks fail. Databases time out. External APIs become unavailable. Users send unexpected data.
These scenarios should be handled intentionally.
We avoid silently swallowing errors.
Applications should:
Return meaningful errors
Log enough information for troubleshooting
Avoid leaking internal implementation details
Avoid exposing stack traces in production
Handle expected failure cases gracefully
Retry operations only where appropriate
Good error handling can turn a difficult production issue into something that can be diagnosed quickly.
12. Logging Should Help Us Understand Production
Logs should exist for a reason.
Useful logs should help engineers answer questions such as:
What happened?
When did it happen?
Which service was involved?
Which request caused the problem?
Which user or system action triggered it?
Can the request be traced across services?
For distributed applications, structured logging and correlation IDs can make troubleshooting significantly easier.
At the same time, logs must not become another security vulnerability.
Sensitive values such as passwords, API keys, access tokens, private credentials, or unnecessary customer information should not be written to logs.
13. CI/CD Acts as a Quality and Security Gate
Standards are much more effective when they are automated.
Rather than depending on every developer to manually remember every check, our pipelines can enforce many of them automatically.
A typical pipeline may look like:
Install dependencies
↓
Formatting check
↓
Linting
↓
Type checking
↓
Automated tests
↓
SonarQube analysis
↓
Application build
↓
Container image build
↓
Trivy image scan
↓
DeploymentThe exact pipeline varies depending on the application and technology stack.
Not every project requires exactly the same stages.
However, the principle remains consistent:
If a quality or security rule can be reliably verified by a machine, it should be automated wherever practical.
This helps prevent basic issues from reaching production and allows engineers to focus more attention on problems that require human judgment.
14. Environment Configuration Should Stay Separate From Code
Development, staging, and production environments often require different configuration.
Application code should not contain hardcoded environment-specific values.
Configuration such as:
Database URLs
API endpoints
Feature flags
Service credentials
Cloud configuration
Environment-specific settings
should be managed independently from application logic.
This reduces the risk of accidentally deploying development configuration or credentials into production.
15. Production Changes Need More Care
Software working locally does not automatically mean it is production-ready.
Before deploying significant changes, engineers should consider:
Database migrations
Backward compatibility
Existing API consumers
Infrastructure impact
Resource usage
Rollback strategy
Monitoring
Failure scenarios
Security impact
For important systems, being able to safely reverse a release can be just as important as deploying it.
16. Architecture Should Solve Real Problems
Modern engineering provides a huge number of technologies and patterns.
Using more technology does not automatically make a system better.
We prefer the simplest architecture that can reliably meet the application's requirements.
Technologies such as:
Microservices
Kubernetes
Event-driven architectures
Message queues
Serverless systems
Distributed databases
Complex caching layers
can all be valuable when they solve a real problem.
They also introduce additional operational and development complexity.
Architecture should be driven by requirements, scalability, reliability, security, and maintainability—not by trends.
Our Engineering Principle
Across these practices, one principle summarizes how we approach software development at Qbits:
Automate what machines can verify. Review what requires engineering judgment.
Tools such as Prettier, ESLint, Ruff, and Biome help us maintain formatting and coding consistency.
SonarQube helps us continuously inspect code quality and security.
Trivy helps us identify vulnerabilities in container images before deployment.
Automated testing helps protect existing functionality.
CI/CD pipelines help enforce quality and security gates.
Code reviews bring human engineering judgment into the development process.
Together, these practices help us build systems that are not only functional today, but remain understandable, secure, scalable, and maintainable as they grow.
Because good software engineering is not about getting code to work once.
It is about making sure the next engineer can understand it, trust it, change it, and deploy it safely.
— Qbits Engineering




