This document defines the coding standards and style guidelines for the KAI project. Following these guidelines will help maintain consistency across the codebase and make it easier for developers to read, understand, and contribute to the project.
- Naming Conventions
- Formatting and Braces
- Comments and Documentation
- Error Handling
- Modern C++ Features
- Project-Specific Patterns
- Names should be descriptive and convey meaning
- Avoid abbreviations unless they're widely understood
- Maintain consistency with existing patterns in the codebase
- Use
PascalCasefor class names, type names, and type aliases - Class names should be nouns or noun phrases
// GOOD
class StorageBase;
class Object;
class BinaryStream;
using HandleSet = std::set<Handle>;
// BAD
class storageBase;
class object_class;- Use
camelCasefor variable names - Private member variables should use trailing underscore:
memberVariable_ - Protected/public member variables should use camelCase without underscore
// GOOD
int localVariable;
Dictionary dictionary;
Registry* registry;
// Member variables
class Example {
private:
int privateValue_;
std::string name_;
public:
bool isEnabled;
};
// BAD
int LocalVariable;
Dictionary Dictionary;
registry* Registry;- Use
PascalCasefor public methods (to match the KAI codebase convention) - Methods that return bool should use
IsorHasprefix when appropriate
// GOOD
void SetValue(int value);
bool IsValid() const;
bool HasChild(const Label& label) const;
// BAD
void set_value(int value);
bool valid() const;- Use
PascalCasefor enum names and enum values - Use
ALL_CAPSfor macro definitions
// GOOD
enum class Color { Red, Green, Blue };
#define KAI_THROW_0(x) throw Exception::x()
// BAD
enum class color { red, GREEN, blue };
#define kaiThrow0(x) throw Exception::x()- Use
PascalCasefor namespace names - Use the
KAI_BEGINandKAI_ENDmacros for the main KAI namespace
// GOOD
KAI_BEGIN
// code here
KAI_END
namespace Network {
// Networking code
}
// BAD
namespace network {
// Networking code
}- Use 4 spaces for indentation (not tabs)
- No trailing whitespace at the end of lines
- Use a single space after keywords like
if,for,while - No space between function name and opening parenthesis
- Use a space after commas in argument lists
// GOOD
if (condition) {
DoSomething();
}
void Function(int arg1, int arg2);
// BAD
if(condition){
DoSomething();
}
void Function ( int arg1,int arg2 );- Use K&R style braces with the opening brace on the same line as the statement
- Always use braces for control structures, even for single-line bodies
- Empty functions can have braces on the same line
// GOOD
if (condition) {
DoSomething();
} else {
DoSomethingElse();
}
void EmptyFunction() {}
// BAD
if (condition)
DoSomething();
if (condition)
{
DoSomething();
}- Aim for a maximum line length of 80 characters
- When wrapping lines, indent continuation lines by 4 spaces
- Break lines after operators, not before them
// GOOD
SomeLongFunctionName(argument1, argument2,
argument3, argument4);
if (veryLongConditionThatNeedsToBeWrapped &&
anotherPartOfTheCondition) {
// Code
}
// BAD
SomeLongFunctionName(argument1, argument2
, argument3, argument4);
if (veryLongConditionThatNeedsToBeWrapped
&& anotherPartOfTheCondition) {
// Code
}- Write code that is self-documenting when possible
- Comments should explain "why", not "what" (the code shows what)
- Keep comments up-to-date with code changes
- Use complete sentences with proper punctuation for comments
- Each source file should begin with a
#pragma once(for headers) - No copyright banners or license information in individual files (in LICENSE file)
#pragma once
#include <KAI/Core/Base.h>
// Rest of includes- Use descriptive comments for non-obvious functions
- Document parameters, return values, and exceptions where needed
- For complex functions, describe the algorithm or purpose
// Retrieves an object from storage and ensures it exists
// Throws NullObject if the object handle is invalid
StorageBase& GetStorageBase(Object const& object);- Document the purpose of the class and any design considerations
- Document any special patterns or usage requirements
// A Value<> has direct access to the storage of an object.
// This means that accessing the value is faster as there is no
// lookup required. However, it is unsafe to use a Value over multiple
// frames, as the storage could be deleted by the garbage collector.
// So it is only really safe to use a Value<> in local scope,
// unless you can be sure that the corresponding Object will not
// be collected.
template <class T>
struct Value : ConstValue<T> {
// Implementation
};- Use
// TODO: descriptionfor code that needs future attention - Include enough context for another developer to understand the task
// TODO: Implement caching to improve performance of repeated lookups- Use the KAI_THROW macros for throwing exceptions
- Use descriptive exception types from the Exception hierarchy
- Check preconditions and invariants consistently
// GOOD
if (!Valid())
KAI_THROW_0(NullObject);
if (stream.GetRegistry() == 0)
KAI_THROW_1(Base, "NullRegistry");
// BAD
if (!Valid())
throw "Invalid object"; // Non-specific exception- Use return values to indicate success/failure when appropriate
- Return
boolfor simple success/failure indications - Return
Object()for null/empty objects rather than null pointers
// GOOD
bool Has(const Label& label) const;
Object Get(const Label& label) const; // Returns empty Object if not found
// BAD
Object* Get(const Label& label) const; // Returning raw pointer- Validate arguments at the beginning of functions
- Use assertions (in debug builds) for invariant checking
- Use early returns to reduce nesting
// GOOD
void SetValue(int value) {
if (!Valid()) {
KAI_THROW_0(NullObject);
}
// Implementation
}
// BAD - deep nesting
void SetValue(int value) {
if (Valid()) {
if (value > 0) {
if (someOtherCondition) {
// Implementation
}
}
}
}- Use C++11/14/17 features where appropriate
- Prefer standard library containers and algorithms over custom implementations
- Use
nullptrinstead of0orNULLfor pointers - Use
autowhere it improves readability, but not where it obscures types
- Use
std::shared_ptror KAI'sPointer<T>for shared ownership - Use
std::unique_ptrfor exclusive ownership - Avoid raw pointers for ownership (use for non-owning references)
// GOOD
std::unique_ptr<Object> CreateObject();
void ProcessObject(Object* obj); // Non-owning reference
// BAD
Object* CreateObject(); // Unclear ownership- Use lambdas for short callbacks or when it improves readability
- Capture only what is needed, prefer capture by value for simple types
- Use meaningful names for lambda parameters
// GOOD
std::sort(items.begin(), items.end(),
[](const Item& a, const Item& b) { return a.GetValue() < b.GetValue(); });
// BAD
std::sort(items.begin(), items.end(),
[&](auto& x, auto& y) { return x.GetValue() < y.GetValue(); }); // Captures too much- Prefer range-based for loops over index-based when possible
- Use
const auto&for non-modifying access to elements - Use
auto&when modifying elements
// GOOD
for (const auto& item : collection) {
Process(item);
}
for (auto& item : collection) {
item.Update();
}
// BAD
for (size_t i = 0; i < collection.size(); ++i) {
Process(collection[i]);
}- Use the
KAI_BEGINandKAI_ENDmacros to wrap code in the KAI namespace - Use
KAI_NAMESPACE()macro when referring to elements in the KAI namespace from inside the namespace
KAI_BEGIN
void Function() {
// Call another function in the KAI namespace
KAI_NAMESPACE(OtherFunction)();
}
KAI_END- Use
Objectclass for type-erased objects - Use
Value<T>orConstValue<T>for type-safe direct access - Check object validity with
Exists()orValid()methods before use
// GOOD
if (object.Exists()) {
Value<int> value(object);
*value = 42;
}
// BAD
Value<int> value(object); // Not checking validity
*value = 42; // May throw- Objects should be managed by the Registry
- Use
SetManaged(true)for objects that should be garbage collected - Avoid manual memory management when possible
// GOOD
Object object = registry->New<SomeType>();
object.SetManaged(true);
// BAD
SomeType* obj = new SomeType(); // Manual memory management- Use the KAI type system for reflectable types
- Register types with the Registry
- Use
KAI_TYPE_TRAITSmacro to declare type traits
// GOOD
KAI_TYPE_TRAITS(MyType, Number::MyType,
Properties::StringStreamInsert | Properties::BinaryStreamInsert);
// Registration
void MyType::Register(Registry& registry) {
ClassBuilder<MyType>(registry, "MyType")
.Method("Method1", &MyType::Method1)
.Property("Property1", &MyType::GetProperty1, &MyType::SetProperty1);
}#pragma once
#include <KAI/Core/Base.h>
KAI_BEGIN
// A simple data container class with managed storage
class DataContainer {
private:
std::vector<int> values_;
std::string name_;
bool isValid_;
public:
DataContainer() : isValid_(false) {}
explicit DataContainer(const std::string& name)
: name_(name), isValid_(true) {}
void AddValue(int value) {
if (!isValid_) {
KAI_THROW_0(InvalidOperation);
}
values_.push_back(value);
}
bool IsValid() const { return isValid_; }
const std::string& GetName() const { return name_; }
void SetName(const std::string& name) { name_ = name; }
std::vector<int> GetValues() const { return values_; }
static void Register(Registry& registry) {
ClassBuilder<DataContainer>(registry, "DataContainer")
.Method("AddValue", &DataContainer::AddValue)
.Method("IsValid", &DataContainer::IsValid)
.Property("Name", &DataContainer::GetName, &DataContainer::SetName)
.Property("Values", &DataContainer::GetValues);
}
};
KAI_TYPE_TRAITS(DataContainer, Number::DataContainer,
Properties::StringStreamInsert | Properties::BinaryStreamInsert);
KAI_ENDKAI_BEGIN
Object CreateDataContainer(Registry& registry, const std::string& name) {
if (name.empty()) {
KAI_THROW_1(InvalidArgument, "Container name cannot be empty");
}
Object container = registry.New<DataContainer>(name);
container.SetManaged(true);
return container;
}
void ProcessDataContainers(const std::vector<Object>& containers) {
for (const auto& container : containers) {
if (!container.Exists() || !container.IsType<DataContainer>()) {
continue;
}
ConstValue<DataContainer> dataContainer(container);
if (!dataContainer->IsValid()) {
continue;
}
// Process the container
std::cout << "Container: " << dataContainer->GetName() << std::endl;
for (int value : dataContainer->GetValues()) {
std::cout << " Value: " << value << std::endl;
}
}
}
KAI_ENDThis style guide is a living document and may be updated as the KAI project evolves. Developers should follow these guidelines for all new code and aim to update existing code to conform when making modifications.