This document defines coding conventions for the SquidStd project. It is intentionally strict to keep the codebase consistent and readable.
- Prefer clarity over cleverness.
- Keep domain boundaries explicit.
- Keep files small and focused.
- Avoid hidden magic and implicit behavior.
- Write code that is easy to reason about during debugging.
Namespace must match folder path exactly.
src/SquidStd.Core/Services/ConfigService.cs → namespace SquidStd.Core.Services;
src/SquidStd.Service/Subscribers/SocketBroadcastSubscriber.cs → namespace SquidStd.Service.Subscribers;
tests/SquidStd.Tests/Core/EventBusServiceTests.cs → namespace SquidStd.Tests.Core;
Group by domain first, not by technical suffix.
| Bucket | Content |
|---|---|
Types |
Enums, type constants (domain-prefixed) |
Data |
DTOs, records, simple data carriers |
Data.Config |
Configuration models |
Data.Notifications |
Notification DTO |
Data.Internal.* |
Internal-only data models |
Interfaces |
Contracts only |
Services |
Service implementations |
Internal |
Implementation details not part of public API |
Subscribers |
IEventBus subscriber classes |
DBus |
D-Bus interface definitions |
- One
.csfile must contain at most one primary type (class,record, orenum). - File name must match type name.
- Use file-scoped namespaces.
- Do not use primary constructors.
- Do not use expression-bodied constructors (
public X(...) => ...); constructors must always have a body{ }.
Inside a type, use this order:
constfieldsprivate readonlyfields (prefixed_)- Non-readonly fields
- Properties
- Constructor(s)
- Public methods
- Protected methods
- Private methods
Dispose/finalization methods (always last)
All private readonly fields must start with _:
private readonly IEventBus _eventBus;
private readonly DirectoriesConfig _directoriesConfig;If a class implements IDisposable or IAsyncDisposable, Dispose/DisposeAsync must be the last method(s) in the file.
- Interfaces live only under
Interfacesnamespaces. - Every interface must have XML docs (
///). - Interface names must use
Iprefix and clear domain naming.
- Enums must live under a
Typesnamespace for their domain. - Always include the domain in the enum name.
// Types/LogLevelType.cs
namespace SquidStd.Core.Types;
public enum LogLevelType { ... }
// Types/DirectoryType.cs
namespace SquidStd.Core.Types;
public enum DirectoryType { Scripts, Logs, Plugins, Configs }- Always use
string.Emptyinstead of"".
- Use Serilog statically via
Log.ForContext<T>(). Do not injectILogger<T>via DI. - Declare the logger as a
private readonlyfield initialized inline.
private readonly ILogger _logger = Log.ForContext<MyService>();- When both Serilog and Microsoft.Extensions.Logging are in scope (e.g.,
SquidStd.Servicewhich usesMicrosoft.NET.Sdk.Web), add a using alias to resolve the ambiguity:
using Serilog;
using ILogger = Serilog.ILogger;- Use static message templates; never use string interpolation for structured logs.
- Keep template shape stable across calls.
- All event types must implement
ISquidStdEvent. - Use
IEventBus.Subscribe<T>to register handlers; useIEventBus.PublishAsync<T>to emit events. - Subscriber classes live in
Subscribers/and register themselves in the constructor.
// Pattern: SocketBroadcastSubscriber
internal class MySubscriber
{
public MySubscriber(IEventBus eventBus, ...)
{
eventBus.Subscribe<Notification>(HandleAsync);
}
}- Plugins implement
ISourcePlugin(Id in reverse-domain format:com.github.author.SquidStd.plugins.name). - Plugins receive an
IPluginContext— usecontext.EventBusto publish,context.Loggerto log,context.ConfigPathfor config. - Plugin hosts are loaded via
PluginLoadContext(AssemblyLoadContext(isCollectible: true)) for hot-reload support.
- D-Bus proxy interfaces live in
DBus/and must bepublic(required by Tmds.DBus runtime proxy generation via Reflection.Emit). - Annotate with
[DBusInterface("...")]and inheritIDBusObject.
- Background services implement
IHostedService(or extendBackgroundService). - If an optional external dependency (e.g., D-Bus session bus) is unavailable at startup, log a
Warningand return cleanly — do not crash the host. - Subscribers that are not hosted services are registered as singletons and force-resolved in
Program.csto trigger constructor subscription registration.
tests/SquidStd.Tests/<Domain>/<Subdomain>/<SubjectName>Tests.cs
namespace SquidStd.Tests.<Domain>.<Subdomain>;
Examples:
tests/SquidStd.Tests/Core/EventBusServiceTests.cs → namespace SquidStd.Tests.Core;
tests/SquidStd.Tests/Service/UnixSocketServerTests.cs → namespace SquidStd.Tests.Service;
tests/SquidStd.Tests/Support/FakeSourcePlugin.cs → namespace SquidStd.Tests.Support;
- File:
<SubjectName>Tests.cs - Class:
<SubjectName>Tests - One main test class per file.
- Test method style:
Method_Scenario_ExpectedResult.
- Shared fakes, builders, and helpers go in
tests/SquidStd.Tests/Support/. - Do not mix reusable test infrastructure into domain test files.
SquidStd.Service.csproj exposes internals to SquidStd.Tests via:
<InternalsVisibleTo Include="SquidStd.Tests"/>- Use Conventional Commits (
feat:,fix:,refactor:,test:,docs:, etc.). - Scope commits to the affected subsystem:
feat(dbus):,fix(socket):,test(eventbus):. - Never add
Co-Authored-By: Claudeto commits.
- No dead code.
- No TODO comments without a tracked follow-up.
- No inconsistent naming across domains.
- Keep warnings under control; do not normalize noisy warnings.
- No literal
""— usestring.Empty. - No primary constructors.
- No expression-bodied constructors.
Nullability
- Use nullable reference types consistently.
- Avoid null-forgiving (
!) unless explicitly justified.
Async naming
- Async methods must end with
Async. - Include
CancellationTokenon I/O-bound public async methods.
Exception handling
- Use guard clauses (
ArgumentNullException.ThrowIfNull, etc.). - Do not swallow exceptions silently.
Collection exposure
- Expose
IReadOnlyList<>orIReadOnlyDictionary<>where mutation by callers is not intended.
Test naming
- Prefer
Method_Scenario_ExpectedResult. - Keep tests focused on a single behavior.
No magic numbers
- Replace protocol/timing literals with named constants.
Using directives
- Keep usings ordered: system first, then third-party, then project namespaces.
- Add using aliases when a name is ambiguous across two libraries in scope.