mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-08-17 09:21:41 +08:00
12 KiB
12 KiB
Coding Convention
In general, here is my preference for any languages:
- You are recommended to debug the compiled binary:
- Once it crashes.
- When after one attemp of failed guessing to fix.
- But respect to
Project.mdfirst.
- I am a fan of crash early. When something should happen, it should just happen, do not play a game like "what if it is not the case" and silently covers the issue. One example is that, if an object should not be null, then we should just use it, if a nullable object should not be null, we should just cast it. No test is performed in this case, using it will crash if it is null, and we know there is a problem. Fix the actual problem instead of doing "error tolerance".
- I am a fan of DO NOT REPEAT YOURSELF (DRY).
- DRY focus on not repeating information in source code. For example, compiler always do name mangling, but name mangling is complex. If you implementaion the mangling in two different places, you repeat the information twice. Therefore a function for such thing is always needed.
- DRY does not focus on not repeating some code. For example, create
json::JsonStringrequires filling its field. The way to creat it does not offer any new information. So a three-lines function just to createjson::JsonStringand copy the argument to its field is not needed.- But if building an AST requires significantly more lines of code, extracting functions for the work is preferred.
- DRY requires finding if a feature has already been implemented somewhere else before implementing it. avoiding massive duplication.
- If the existing implementation is not sharable, refactoring is preferred.
C++ Coding Convention
- Although C++ does not require this but we want to have
externon all function forward declarations.- In general we don't use
inlinein header files unless such function is performance critical, e.g. very simple comparison operators.
- In general we don't use
- Rules for C++ header files:
- Guard them with macros instead of
#pragma once. - In a class/struct/union declaration, member names must be aligned in the same column at least in the same public, protected or private section.
- Keep the coding style consistent with other header files in the same project.
- Guard them with macros instead of
- Extra Rules for C++ header files in
Sourcefolder:- Do not use
using namespacestatements; the full names of types are always required.
- Do not use
- Rules for cpp files:
- Use
using namespacestatement if necessary to prevent from repeating namespace everywhere. vl::stream::is an exception, always usestream::withusing namespace vl;, DO NOT useusing namespace vl::stream;.
- Use
Basic C++ Library Leveraging
- This project uses C++ 20, you are recommended to use new C++ 20 features aggressively.
- All code should be cross-platform. In case when an OS feature is needed, a Windows version and a Linux version should be prepared in different files, following the
*.Windows.cppand*.Linux.cppnaming convention, and keep them as small as possible. - DO NOT MODIFY any source code in the
Importfolder, they are dependencies. - DO NOT MODIFY any source code in the
Releasefolder, they are generated release files. - You can modify source code in the
SourceandTestfolder. - Use tabs for indentation in C++ source code.
- Use double spaces for indentation for JSON or XML embedded in C++ source code.
- Use
autoto define variables if it is doable. Useauto&&when the type is big or when it is a collection type. - The project only uses a very minimal subset of the standard library. I have substitutions for most of the STL constructions. Always use mine if possible:
- Always use
vintinstead ofint. - Always use
L'x',L"x",wchar_t,const wchar_tandvl::WString, instead ofstd::stringorstd::wstring. - Always use
FilePathfor file path operations. - Use my own collection types vl::collections::* instead of std::*
- Check out
REPO-ROOT/.github/KnowledgeBase/Index.mdfor more information on how to choose the correct C++ data types.
- Always use
- To attach availability semantic to a value:
- If any number is expected to be valid only when non-negative, you could use
-1to represent invalid value. - If an object is expected to be valid only when non-null, you could use
nullptronT*orPtr<T>to represent invalid value. - Use
Nullable<T>to represent any invalid value if possible.- DO NOT use
Nullable<T*>,Nullable<Ptr<T>>orNullable<Nullable<T>>, this is too confusing.
- DO NOT use
- Only when there is no other choice, use an extra
boolvariable.- This could happen when "null" semantic is valid.
- If any number is expected to be valid only when non-negative, you could use
Regular Expression
Regular expression utilities are offered by vl::regex::Regex, here are important syntax differences from other regular expression implementations:
- "." means the dot character, "/." or "." (or "\." in C++ string literal) means any character.
- Both "/" and "" escape characters, you are recommended to use "/" in C++ string literals.
- Therefore you need "//" for the "/" character and "/\" or "/\\" for the "" character in C++ string literals.
- Constructing a
Regexobject is expensive. If a regular expression is used multiple times or multiple places, make a variable to reuse it, but it should not be a global variable.
Creating and Using Parsers
When VlppParser2 is available to the current project, complex parsers always require to use VlppParser2. There are already existing parsers, especially XML and JSON.
- Each parser has a generated
Parserclass, you are always required to use the last piece of namespace with it, e.g.xml::Parserandjson::Parser.glr::xml::Parserandglr::json::Parseris also equally good. - Some parsers like XML/JSON has its own parse function
XmlParseDocument,XmlParseElement,JsonParse, it has extra preprocessing, they are always required to use instead of usingxml::Parserdirectly.- Only if such functions cannot be found for a certain parser, the
Parserclass can be used directly.
- Only if such functions cannot be found for a certain parser, the
- Creating a
Parserclass is super expensive, you must do your best to share it across the project:- Any
Parserclass is re-entrant, you can run it parallelly in multiple threads. - Any unit test project should already have a way to share involved parsers. You are recommended to follow the pattern if you need to use a new parser.
- The usual pattern would be having a pointer to that parser as a global variable, and a pair of functions for lazy initialization or finalization. And the main function will explicitly call the finalize function to avoid messing up memory leak detection.
GacUIproject has a mechanism to register parsers dynamically.
- Any
- Each parser should already provide functions for converting AST back to string, you should not invent it by your own, unless you are making a new parser.
- Each parser should already provide multiple visitors, try to reuse them. To invent your own algorithm, especially recursive algorithm, you should always try to create visitors.
Advanced C++ Coding Rules
- DO NOT make helper functions that are only used once, especially if they are only called in one destructor.
- DO NOT make global variables with types that carry constructors or destructors, even when they are implicit.
- This could mess up the order of initialization, finalization or memory leak detector.
- One exception is
WStringwhich is initialized usingWString::Unmanaged; such constructors and destructors do not do memory management. - Another exception is
Pair,Nullable,VariantorTuplewith valid types here. - If pointers are needed, you could only use
T*and do initialization or finalization explicitly. All such objects should be destroyed inmain,wmain,WinMainorGuiMain, before memory leak detector runs.
- DO NOT reset any raw/shared pointer member to nullPTR in destructorS.
- Prefer the latest C++ features (up to C++ 20).
- Prefer template variadic arguments, over hard-coded-counting solutions.
for Thread Safe Programming
- Most of the code do not require thread safety, DO NOT over engineering.
SpinLockis only for protecting a piece of code or data in a super flashy short time.- When defining a
SpinLockfield, names it begins withlock, a// covers a, b, ccomment is recommended to put above it, a empty line is recommended to put around it. - When doing
read-process-writebut thewritepart doesn't depend on the result ofprocess:- The below rules only involve when
processis heavy, if anything is simple, keep it simple. - You can copy or move the heavy structure in
SPIN_LOCK, andprocessafterSPIN_LOCK. - Especially for scenario when a container should be processed and cleared,
std::movewould be the best choice to copy and cleanVlppcontainers. Using movedVlppcontainers is not undefined behavior.
- The below rules only involve when
- When defining a
- When using other locks, try your best to only use methods that available to all platforms.
- Write cross platform code when it is performance optimal for all platforms.
- If Windows specific methods could make Windows implementation much better, then you are allowed to implement them in different ways.
- Use
lock(bothSpinLockandCriticalSection),mutex,semaphore,event,semaphore,rwlock,cvas lock variable prefixes.
- Prefer lock free construction only when the code would be simple, DO NOT involve complex lock free trick unless explicitly required.
std::atomic<T>should be considered and use it precisely. The code should be correct when running parallelly while I don't want unnecessarystd::atomic<T>.atomic_vintis widely used in the library, use it forvint.
- Avoid polling at all cost, I strongly prefer scheduling in async way.
for Reflectable Types
- Any interface or class
Xshould inherit fromvl::reflection::Description<X>.- If such a class (not including interface) should be inheritable in Workflow script, use
AggregatableDescriptioninstead ofDescription.
- If such a class (not including interface) should be inheritable in Workflow script, use
- No
constis allowed for methods or reference types. - Prefer
IValue*interfaces for container types on interfaces. - Container types and some other types support range-based for loop. Always prefer range-based for loop over other loops.
- You can use
indexed(container)to convert a container of typeTtoPair<T, vint>, to read the correct index. - Avoid using an expression that creates temporary objects in
for(... : HERE)orfor(... : indexed(HERE)). The current C++ destroys the temporary object too early; therefore this becomes UB.
- You can use
- Prefer Inversion of Control (IoC) and other design patterns, over trivial virtual functions, over switch-case on types, over if-else on types.
- Prefer static dispatching over dynamic dispatching when possible and reasonable.
- Unless explicitly instructed:
- You are not allowed to test if
VCZH_DEBUG_NO_REFLECTIONis defined. - You are not allowed to test if
VCZH_DEBUG_METAONLY_REFLECTIONis defined. - You are not allowed to call any function that does not work with
VCZH_DEBUG_NO_REFLECTION. - Reflection registration is an exception follow the document for recommended patterns.
- You are not allowed to test if
Keep C++ Code Cross Platform
- All source files must aim for cross platform unless the file name has
.Windowsor.Linux.. - Use FilePath to normalize file path, for file path operations and delimiter access.
Workflow Script Coding Convention
- Avoid explicit type specification whenever possible:
- Prefer
var v = e;wheneverTcan be omitted. - Prefer
var v : T = e;overvar v = e over T;ifTcannot avoid. - When implicit type conversion works at the place, avoid
cast,asandinferexpression. - Prefer
cast *overcast Twhen the context acceptsT.
- Prefer
- Nested
try-catchandtry-finallycan be merged into one singletry-catch-finallystatement. - Prefer strong typed collections in Workflow, but when writing C++ reflectable interfaces, use
Ptr<IValue*>.
Workflow Script Generation in C++
- When generating Workflow script, avoid building text, you should always build the AST. The AST type for a complete Workflow script module is
WfModule.