# Socially Distant Code Style Code written for Socially Distant, above all else, must be human. This means that all code in the game should be easily understood by any human being with a reasonable grasp of programming concepts. It should not feel confusing to read for someone with reasonable programming skill. On this page, you'll learn how we write code to reflect that. ## Functioning code is good code. To put it bluntly, it doesn't matter one bit how well-written or "clean" your code is if it doesn't fucking work. Someone will eventually need to read, understand, and debug your code, so it should always be written with that in mind. This boils down to these core principles: - **Premature optimization is the root of all evil**: Wasting time optimizing your code for a computer before its functionality has actually been battle-tested will most likely result in you having optimized a bug. That bug is still a bug, and will need to eventually be found and fixed. Do not worry about optimizing your code at the cost of readability unless it both functions as-is and its current implementation demonstrably harms the game's performance. - **Complexity causes problems:** Writing complex APIs and abstractions makes troubleshooting a bug more complex. Always focus on getting your code to _work_ before building a complex system around it. If complexity is needed, it will grow organically as your feature becomes more well-integrated with the rest of the codebase. - **Code fixes problems, code doesn't fix code.** If you feel yourself trying to work around part of the game's code, there is a problem with someone's code. You should work to fix that problem directly, not work around it. Sometimes, the problem isn't actually in your own code. ## Naming things The names of classes, variables and methods should, above all else, be understandable by humans. Out of all things on this page, naming rules in Socially Distant are the most strict. This is because your code will be read by a screen reader, and must be understood when spoken aloud. ### Ensuring names can be spoken aloud easily You **must** ensure that your names can be spoken aloud by a text-to-speech voice. Here's how: 1. **Avoid abbreviating things.** Prefer `numberOfPeople` over `numPeople`. 2. **Don't use acronyms.** Prefer `fileSystem` over `fs`. 3. **Use proper capitalization:** Local variables and private variables use `camelCase`, everything else uses `PascalCase`. Improper capitalization causes screen readers to guess, which makes things harder to read. 4. **Include unit names when necessary**: Prefer `angleInRadians` over `angle`, or `timeoutInSeconds` over `timeout`. ### Appropriate single-letter names, and their assumed meaning You should never use single-letter names if you can avoid it, as they violate the above rules for screen reader compatibility. Names in this list are acceptable, as long as they're used in the correct context. | **Name** | **Meaning** | **Valid context** | |:-----------------:|:----------------------------------------------------------|:-----------------------------------------| | `i` | Loop counter, or "index" | Use when iterating through a collection. | | `j` | Nested loop counter | Same as `i`, but for nested loops. | | `x`, `y`, `z`, `w` | Components of a 2D, 3D, or 4D vector, or of a quaternion. | Math | | `e` | Euler's constant | Math | | `e` | Event arguments | Event listeners and callbacks | | `T` | Any class, structure, or other type | Type parameters and generics | Please note that `r`, `g`, `b` and `a` (color components) are intentionally absent from that list. You should use `red`, `green`, `blue`, and `alpha` as names instead. ## Nullability Socially Distant uses nullable reference types. Please write your code with nullability in mind. Do not opt out of nullability with `#nullable disable` under any circumstances, fix your code instead. ## Type structure When defining any type, such as a class, please use these conventions: ### Access level Access levels should always be specified explicitly. (`internal class` vs. just `class`). All API defined in Socially Distant should use the least-permissive access modifier necessary for the code to function. If it doesn't need to be `public`, it should not be `public`. ### Always mark as `sealed` by default Most types in Socially Distant do not need to be inherited, and this should be communicated. When defining a class, you should always mark it as `sealed` until someone actually needs to inherit it in another type. This has [performance implications](https://youtu.be/d76WWAD99Yo) and there is no reason to leave an object open for inheritance if it need not be inherited. ### Member ordering When defining the members of a type, please follow this layout to ensure better readability. - Fields - Properties - Events - Methods - Nested types Then, order by access level: - Public - Protected / Internal - Private Then, for fields, order by mutability: - Compile-time constant (`const`) - Read-only (`readonly) - Writable ## Attributes Attributes are types that you can use to apply special *attributes* to certain parts of the code. When applying an attribute to a type or member, always keep attributes on their own lines directly above the member. This is good: ```csharp [Attribute] public sealed class Class { [Attribute] private float field; [Attribute] public void Method() { this.field += 1; } } ``` This is bad: ```csharp [Attribute] public sealed class Class { [Attribute] private float field; [Attribute] public void Method() { this.field += 1; } } ``` When adding multiple attributes to something, each attribute should be on a separate line: ```csharp [Attribute1] [Attribute2] [Attribute3] private float field; ``` Attributes on method parameters aren't affected by this rule - because they're rare. ## Type parameters and Generics You will run into situations where generics are extremely useful, if not required, as part of your API. Remember to use descriptive names when writing generic types. Use type constraints and good naming to ensure the correct types are specified by users of your API. ## Interfaces vs. Abstract Types Socially Distant's API uses interfaces all over the place. When designing a system, you should take advantage of this. Socially Distant uses interfaces to define what a given type must be able to do in order for that type to be valid as part of another API. We do not care *how* a given thing is done, so long as the implementation conforms to an interface's requirements. A good example of this is `IComputer` for representing an in-game computer. It describes what a given type must be able to do in order to be considered a computer. It does not make any concrete assertions on how the computer accomplishes its goal of being a computer. We do **not** use abstract types unless there is mandatory concrete behaviour associated with the abstract type. A good example of this is `Widget`, since all widgets must be able to perform a layout update and must be able to be drawn to the screen, and both these behaviours need to generally behave the same across all widgets. In other words, if you define an abstract class that only has `abstract` members with no `virtual` or concrete ones, then it should instead be defined as an interface. This is an appropriate use case of abstract types: ```csharp public abstract class Animation { private readonly float durationInSeconds; private float progressInSeconds; protected Animation(TimeSpan duration) { this.durationInSeconds = (float) duration.TotalSeconds; } public void Update() { float progress = MathHelper.Clamp(progressInSeconds / durationInSeconds, 0, 1); OnUpdate(progress); progressInSeconds += Time.DeltaTime; } protected abstract void OnUpdate(float progressPercentage); } ``` This should be an interface: ```csharp public abstract class Animation { public abstract TimeSpan Duration { get; } public abstract void Update(); } // Should instead be public interface IAnimation { public TimeSpan Duration { get; } public void Update(); } ``` Sometimes, it's a good idea to have both an interface and an abstract type implementing that interface. The abstract type should implement the interface, so that your API can accept other implementations of said interface. ```csharp public interface IAnimation { public TimeSpan Duration { get; } public float Progress { get; } public bool IsActive { get; } public void Update(); public void Cancel(); } public abstract class Animation : IAnimatio { private readonly TimeSpan duration; private double progressInSeconds; private bool isComplete; private bool isCanceled; public TimeSpan Duration => duration; public float Progress => MathHelper.Clamp((float) (progressInSeconds / duration.TotalSeconds), 0, 1); public bool IsActive => !isCompleted && ~isCanceled; public Animation(TimeSpan duration) { this.duration = duration; } public void Update() { OnUpdate(Progress); if (progressInSeconds >= duration.TotalSeconds) { isCompleted = true; return; } progressInSeconds += Time.DeltaTime; } protected abstract void OnUpdate(float progress); } ``` By designing APIs in this way, when an abstract type is genuinely needed, that type can also be generic. ## Whitespace, braces, nesting,, and line breaks Socially Distant is primarily written in C#, however portions of the game are written in `sdsh` (the Socially Distant Shell language). These formatting rules depend on what language you're writing in. [Learn more about `sdsh`](sdsh/the-basics.md) ### Indentation rules Always use spaces instead of tabs, in both languages. In C# code, use four spaces for each level of indentation. For `sdsh` scripts, use two spaces. ### Brace style In C# code, curly braces always belong on their own lines, i.e. ```csharp public sealed class Class { public void Method() { } } ``` instead of ```csharp public sealed class Class { public void Method() { } } ``` For `sdsh` scripts, do the opposite, i.e: ```sh function sayHello() { say "Hello, $1!" } sayHello Ritchie ``` This is because `sdsh` is an interpreted language, so your scripts will take up less space in RAM and less time to execute. ### Nesting In C#, avoid heavy amounts of nesting. If a method nests code more than three levels deep, you should try to fix that. In `sdsh`, deeper nesting **directly correlates** to the script taking more time to execute. Furthermore, nested functions are defined in global scope as the script is executed, so excessive nesting will cause bugs. ### Line breaks In C#, always separate members with a blank line, except for fields with the same access modifiers. ```csharp public sealed class Class { public const float Constant = 42; public readonly float PublicField; public readonly float PublicField; public readonly float PublicField; public readonly float PublicField; private readonly float Field; private readonly float Field; private readonly float Field; private readonly float Field; public float Property => Field; public bool IsDone => false; public IEnumerable Things { get { yield return "One thing"; yield return "Two thing"; yield return "Red thing"; yield return "Blue thing"; } } public string Name { get => "Ritchie"; set => SetName(value); } public event Action? RitchieWasAngered; public Class() { Field = Constant; } private void SetName(string newName) { RitchieWasAngered?.Invoke(); Log.Error($"My name is {Name}, not {newName}."); } private struct Struct { public int Number; public string String; public bool Boolean; public void DoScaryThing() { throw new NotSupportedException("I cannot be scared."); } } } ``` When writing `sdsh` scripts, separate groups of similar statements with a blank line. ```sh function Function() { command command command if condition; then command Success else command Fail fi } VARIABLE=value VARIABLE=value export ENV=value export ENV=value export ENV=value Function say "An octagon has 8 fantastic sides" say "An octagon also has 8 amazing angles" playSong --no-loop /Career/BGM/Octagonfire once SongFinishedEvent exec "logout --force" ```