-
Visual Studio 2012 is used for development. We also support Visual Studio 2012 Express for Windows, which can be downloaded and used for free! VS2010+NuGet or VS2013 also work fine. Other IDEs and environments are supported occasionally (ask in the forum if you need help).
Also make sure to update to the latest VS2012 Service Pack plus the tools and extensions mentioned below.
-
TortoiseHg and VisualHg for Mecurial source control support (here are some great tips), we also support git, which our server converts from the hg repos!
- Internally we use the Kiln Client, which is based on TortoiseHG and makes some useful additions. Please make sure you install this instead of TortoiseHG (delete mercurial.ini if you have used TortoiseHG before) because this will setup your username and all needed extensions automatically for you. Just see Committing Code below for setting up "Push after commit" and "Update after pull" and you are done.
- CodeLens supports git now via Visual Studio 2013 Update 3. In order to activate it for a cloned hg repository you have to first download the git version of the repo (supported by Kiln, just switch and clone into a new folder) and then copy the .git folder into your normal folder (right besides .hg). Now you can setup VS to use a local git repository via the Team Explorer Settings toolbox and enable CodeLens (just the options with Git in the name). This is how it looks like when done:
-
ReSharper 8 for refactoring (use DeltaEngine.sln.DotSettings) and have the following extensions installed:
- CleanCode (most important extension, has about 10 concepts of Uncle Bobs Clean Code book)
- Greenkeeper (allows to reformat code into newspapermode, which is how we expect code to be, very amazing plugin saving tons of time making classes cleaner and easier to understand), see internal services SetupTools for updated versions (8.2 ReSharper support). Also see the DeltaEngine.DotSettings.xml file for the configuration to match this C# Coding Style document.
- ReSharper PowerToys: Cyclomatic Complexity (in addition to CleanCode a very important extension to make sure methods never exceed complexities of over 20 instructions)
- Agent Mulder (helps with finding types registered via Dependency Injection)
- AgentRalph (code clone detection)
- Enhanced Tooltip (colorizes parameter tooltips)
- Go to Word (find words in the whole solution via Ctrl+T)
- InternalsVisibleTo (improves intellisense for InternalsVisibleTo attribute in AssemblyInfo.cs files)
- Mnemonic Live Templates other templates to help quickly generate code like classes, methods, propertys with a few keystrokes
- NuGet support for ReSharper (preinstalled, adds package references on Alt+Enter, sometimes does not work, so check your references)
- Preview Tab Behaviour (opens files in preview tab to have less files open over time)
- ReSpeller Free (helps with misspellings in method, class or field names or comments)
- TestCop (easily switch between unit tests and code)
- Utility Pack (contains 10 more context actions like duplicate method, invert return value, etc.)
- ReSharper.DictionaryHelper (refactorings for dictionaries, helps to prevent writing bad get code)
- NCrunch for continuous testing and coverage
- .NET Demon for continuous building (with every keystroke). If you don't have ReSharper or NCrunch this free tool can be used for testing: TestAfterBuild
- NUnit for unit testing (via NuGet), Autofac for Dependency Injection plus a few more depending on the platform and frameworks used. All dependencies are just for development, the final output of the AppBuilder has none of them.
- Editor Guidelines (Visual Studio 2013 Extension) helps to show how much space is left in a line by drawing a red dotted line at 100 characters. Very simple and useful.
- Productivity Power Tools 2013 (Visual Studio 2013 Extension) Bunch of very useful extensions (20+), tons of new features all the time.
Optional (e.g. our integration server will run all tests and NDepend whenever commits are made and report back if someone produced warnings):
- We use FogBugz as our planning and bug reporting tool. In the past we also used other tools (like OnTime), but FogBugz works great with external contribution, freelancers and bug reports. You can submit bugs or feature requests directly and participate in the wiki there, with or without an account.
- dotTrace or Ants Profiler for profiling (we use both, each has its benefits)
- ILSpy or Reflector for investigating
- NDepend for code analysis (nightly)
- TeamCity for continuous integration, automated jobs after checkins and builds, tests and reporting. We use a lot of our own internal tools here as well.
- CodeCompare for comparing changes and merging in VS, KDiff, BeyondCompare and other tools are useful too (like compare build into TortoiseHg)
- Code Metrics Viewer 2013 (Visual Studio 2013 Extension), shows you some details about each project (line count, metrics) in a nice tool window
- IDEStatusBarInfos (Visual Studio 2013 Extension) shows you the current CPU and RAM consumption of VS. Helps to see when things go south and VS is stuck and you need to restart it.
- PerfWatson Monitor (Visual Studio 2013 Extension) Shows you how responsive VS is in a nice graph.
- SpeedCop, Visual C++ Refactoring, Solution Dependency Viewer, Microsoft Code Digger, Code Contract Tools, and many more extensions might be useful as well, all depending on what you work on.
Use the default keys for Visual Studio and Resharper. Additionally we like to use F4-F9 for testing, make sure this is always setup on all PCs in the office so everyone can quickly get into working mode when pair programming.
- F4: Debug.StartWithoutDebugging (also Ctrl+F5 by default)
- F5: Debug.Start (start with debugging, default VS key)
- F6: ReSharper.ReSharper_UnitTestRunContext (start test under cursor with Resharper, also Ctrl+U,R)
- Ctrl+F6: NCrunch.Runcoveringtestsuseexistingprocess (start testing, usually reruns last changed test with NCrunch, also Ctrl+M,U)
- Shift+F6: Ncrunch.Debugcoveringtestsuseexistingprocess (like F9 with ReSharper, but with NCrunch instead, helps you to stay focused with the NCrunch tests results output pane, also Ctrl+M,N)
- F7: Build.BuildSelection (Shift+Ctrl+B to Build Solution by default), only needed when .NET Demon or NCrunch messes up because your solution should always be compiled.
- F8: ReSharper.ReSharper_UnitTestSessionRepeatPreviousRun (useful for visual tests after fixing stuff, also Ctrl+U,U)
- Shift+F8: ReSharper_UnitTestSessionRunAllAction (Run all tests with ReSharper, including integration tests)
- Ctrl+F8: NCrunch.RunAllTestsRightNow (Run all tests with NCrunch, normally done automatically, only useful if you disabled automatic test execution, also Ctrl+R+J)
- F9: ReSharper.ReSharper_UnitTestDebugContext (Debug test under cursor with Resharper, also Ctrl+U+D)
- Don't forget about other useful VS hotkeys, especially F12 (Go to definition), Shift+F12 (find all references), Alt+Enter (Fix ReSharper issue) and Ctrl+. (Intellisense) are important.
Note:
Ctrl+F6, Ctrl+F7 and Ctrl+F9 are used by CodingStyleCpp (NCrunch is not used there).
The whole Delta Engine source code is available on
Github and
Codeplex, you can freely fork it and create your own features and branches. If you want us to bring your new features or code improvements into our master branch, just use the appropriate tools at Github or Codeplex to notify us.
Internally we all use
TortoiseHg and connect FogBugz cases (bugs, features, etc.) to commits by writing "Case 123: " into the commit message. Start every commit with one case number at least so we can quickly figure out what is going on. FogBugz and Kiln support git as well, which we utilize for tools like Visual Studio 2013 Code Lens support to tell us who worked on what and look at all changes for each method.
Our process to generate the public
Github and
Codeplex repository is as follows:
- Code is writting in our internal repositories, in many branches (each employee has its own repo with as many branches as needed)
- All team members have to merge with all others in their team before starting the day.
- Branches are merged into the default branch once code works fine and all tests pass automatically. Releases are based on this and should happen at least once a week.
- When the default branch is pushed to our build server, all code is compiled and tested (unit tests, integration tests, etc.)
- Only when everything succeeds the build server creates commits for Github and Codeplex.
- For changes (pull requests, forks, etc.) in Github or Codeplex the team has to merge them back into their branches so they appear on the next public commit.
- Commit in the "firstname lastname <email>" standard hg format, use 68 characters per line and use "update after pull" and "push after commit" settings.
Server code, especially the DeltaEngineServices use newer code and do not have the typical restriction of the normal Delta Engine, which needs to be able to run on all devices. Server code can use the latest language features, any library or external services, databases, etc. without problem. Usually all services are installed as a Windows Service and started automatically by TeamCity when a CI build succeeds. Beta releases are triggered by successful nightly releases or manually and production versions are only released manually after testing, usually by promoting a beta release.
- DateTime on the server side should always be in Utc (which is faster too), especially anything stored in the database should be Utc and region independent. In any database tool used specify the local timezone to display those utc dates properly, which is very useful when simply looking at raw db data.
- Pretty much all services run asyncroniously and use C# async features all the way to the service entry point and for each client request made. All code must run thread-safe and is independent from other client requests.
- Application servers can also run the engine, but in a reduced way, using Mocks for graphics, multimedia, etc. and simply executing the entity processors. Entities marked with ServerComponent are only handled on the server and results are automatically send to all clients.
Naming conventions
Like in the normal engine, but a common pattern for Backend services is to have the interfaces in the main namespace (e.g. .Hosting, .Builder, .Tachyon, etc.) and the default or base implementation in Default (.Hosting.Default, .Builder.Default, etc.) with special implementations in other namespaces (e.g. Mocks, Spys or concrete services).
Example:
- DeltaEngineBackend.{name}
High level API and data types, pretty much interfaces only, this is what is usually referenced except for the implementation project.
- DeltaEngineBackend.{name}.Default
Default Implementations, which provide 90%+ of the needed functionality, but is extensible and can be replaced when needed. Only referenced in projects using this (or connecting the dots) plus tests. In case the default implementation is using a specific framework, that name is used instead (like it is done in the engine for OpenGL, SharpDX, GooglePlayServices, etc.)
- DeltaEngineBackend.{name}.Server
If this namespace provides a service. The same goes for actual implementations like {GameName}.Server, which uses the other projects and implements the server for the game. All this is internally wired via extensions, look up the test projects or any server project to see how easy it works.
Tests are always written first, test classes end with Tests and are in a separate project.
No Test project is allowed to reference any other Test project to keep projects testable independently.
Mocks don't belong into Test projects, create a separate Mock project instant (see
Mock guidance for more information).
Always follow
Clean Code and the
TDD rules:
- 1. Write test code before production code. Work together with a team member to learn, it will require a lot of discipline and training.
- 2. Make tests work and only write what is necessary for the tests to work (no extra code, put research into extra tests or extra projects).
- 3. Refactor and make sure to get 100% coverage.
There are 3 kinds of tests, but all of them can be tested the same way (automatically via NCrunch (highly recommended), visually via ReSharper, or manually via Program.cs). With the
TestWithMocksOrVisually class and the
[Test] attribute you can easily combine automated tests and visual tests, with the
[Test, CloseAfterFirstFrame] attributes you can mark tests as integration tests that will close immediately after running one frame. Both avoid duplicated code as everything in them is shared and tested automatically with Mocks using NCrunch all the time. This is especially true for integration tests, in particular graphics, multimedia, input, etc. tests, which need to be run with each framework separately. NCrunch will make sure that the overall logic works, manual testing is still required to confirm things are visible on screen, sound is working, etc. You can however also test all tests with ReSharper similar to our integration server, this will launch all tests and make sure they work (including making approval screenshots and comparing the result, quickly telling you if something broke).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class DeviceTests : TestWithMocksOrVisually
{
[Test, ApproveFirstFrameScreenshot]
public void DrawRedBackground()
{
Resolve<Window>().BackgroundColor = Color.Red;
}
[Test]
public void SizeChanged()
{
Resolve<Window>().ViewportPixelSize = new Size(200, 100);
Assert.AreEqual( new Size(200, 100), Resolve<Window>().ViewportPixelSize);
}
}
|
Not only does this test all the code with NCrunch automatically, but you can also let NCrunch test all integration tests automatically for you when setting
TestStarter.NCrunchIgnoreSlowTests = false;. This is normally annoying when actually working on code, but still great for code reviews and testing functionality of hundereds of tests quickly before starting to confirm them visually as well.
ReSharper (via F6, F8 or F9) or other test runners also work great together with the
[Test] attribute, which utilizes the resolver in the current framework you are using (e.g. TestWithGLFW3 uses GLFW3Resolver in its
TestWithMocksOrVisually class).
Of course all 3 test types are still available:
- Automatic Unit Tests marked with the Test attribute (tested with NCrunch all day, each test MUST execute in below 10ms (without initialization), otherwise move it to Integration tests).
Note: When testing with ReSharper or NCrunch the first test might take an extra 100-200ms (or more when Moq is used) because of starting up and loading the assemblies for the first time, this can be ignored, all other tests will execute quickly. When just executing a single test, this overhead is also there, so don't use that time to measure automatic unit tests, always run them all.
1 2 3 4 5 6 | [Test]
public void PointToString()
{
var p = new Point(3, 4);
Assert.AreEqual( "(3, 4)" , p.ToString());
}
|
- You can also just write an integration test by itself by using [Test] and the extra Category("Slow") attribute. Anything too slow that still needs testing to cover close to 100% code, especially important for implementation code, should not take longer than 5s, absolute maximum is 60s (also max. for NCrunch) for complex tests. Normally these kinds of tests are used when working with external frameworks or libraries.
1 2 3 4 5 | [Test, Category( "Slow" )]
public void ChangeWindowSize()
{
Start(OpenTKResolver, (Window window) => window.TotalSize = newSize(100, 100));
}
|
- Visual Tests are usually big tests, but anything you want to confirm visually qualifies. Often the [Ignore] attribute is used (for functional testing, tutorials, samples, starter kits, whole programs etc.). There is no time limit since visual tests will usually be aborted by the tester, but startup time should be less than 5s. Note: Ignored Tests are NOT relevant for code coverage, they will always be excluded. All code always has to be covered by automatic unit tests or integration tests. For this reason we highly recommend the [Test] attribute in a class derived from TestWithMocksOrVisually described above!
1 2 3 4 5 6 | [Test, Ignore]
public void StartWholeProgram()
{
Program.Main();
}
|
The following table explains with a few example namespaces where all kind of tests get 100% code coverage (and some extra integration and visual tests):
Namespace | Automatic Unit Tests | Integration Tests | Visual tests | Notes |
Core | X | | | Core functionality can usually be tested pretty easily. There might be 1-2 exceptions to test framework classes like Threading or File access. |
Datatypes | X | | | Datatypes are the prime example on how to test stuff automatically. |
Graphics | X | | | Graphics is an abstract base namespace, so all we can test is mocked implementations of it, there is no real framework implementation we can test. |
Graphics.OpenTK20 | | X | | OpenTK is a concrete implementation of Graphics and just implements all the already tested Graphics classes. Thus no automatic unit tests are required here and they would be too slow anyway (we cannot create a window, opengl device, do something, dispose everything in less than 10ms). Since this should only focus on the framework implementation visual tests also make no sense here and should go to the Rendering namespace instead, which also has the advantage that we can test the same code with different graphic frameworks. |
Graphics.Tests | X | X | X | All graphic frameworks are tested in Graphics.Tests and all automatic unit tests can also be found here. You can start any test visually as well (see above, ReSharper F6 makes this easy) to see what it actually does and easily debug it. |
Input | X | | | Input is an abstract base namespace, so all we can test is mocked implementations of it, there is no real framework implementation we can test. |
Input.Windows | | X | | Provides implementations for Mouse, Keyboard, Touch (Windows 7+) and GamePad support on Windows. Testing happens in Input.Tests in the same way Graphics.Tests works. |
Input.Tests | X | X | X | All input frameworks are tested in Input.Tests like Graphics.Tests tests all graphic implementations. You can easily select any test and run it with any framework available (F6 or expand the test cases to select an ignored test case). |
Rendering | X | | X | Automatic unit tests make sure the code works, the design makes sense and there is nothing useless in it. It uses mocked graphic classes. Integration tests are not required here as we have already tested all integration code in the framework test projects (e.g. Graphics.OpenTK). Visual Tests are for the programmer and testers to confirm there is actually a line, box, 3d model, etc. on screen. |
Rendering.Cameras | X | | X | Automatic unit tests make sure the cameras are all wired up correctly and make sense. Visual Tests are for the programmer and testers to confirm the camera is actually working, rotating, moving, etc. |
Multimedia | X | | | Automatic unit tests are used to define the basic behavior of multimedia base classes. There is nothing visual to test and nothing is implemented here that we can test either. |
Multimedia.OpenTK | | X | | This assembly is tested by integration tests to make sure all framework calls work as expected. |
Multimedia.Tests | X | | X | In order to test multimedia functionality visually and via actual sound output, this assembly contains tests to be tested automatically first (TDD) and visually to confirm there is no glitches and everything is working as advertised. |
Physics | X | | | Automatic unit tests are used to define the basic behavior of phyiscs base classes. There is nothing visual to test and nothing is implemented here that we can test either. |
Physics3D.Jitter | | X | | This assembly is tested by integration tests to make sure all framework calls work as expected. |
Physics2D.Tests | X | | X | This tests 2D physics, either mock implementations (for automatic tests) or concrete frameworks like Farseer to test visually everything works as expected and is finetuned. This is very important for physics and a physics programmer will spend most time here to finetune values. |
Content | X | | | Automatic unit tests on how content is loaded and used throughout the engine. |
Content.Client | | X | | This assembly is tested by integration tests to make sure we can actually get content from the Content Server. |
Blocks | X | | X | And Blocks is a Starter Kit game bringing everything together. Like the rest of the engine it is also written test-first, so all the code is 100% covered by automatic unit tests and it is easy to test the same functionality with visual tests by just switching the implementation. |
Graphics, UI (rendering, scenes) and IO (database, files, content) code is usually not automatically testable, but we still write functional tests first and try to separate UI code from the logic behind, which can be tested automatically and be refactored without impacting functionality of the UI code. Do not check in slow test code. Instead use Mocking (see
Moq) to test slow or external calls and provide functional tests (with the Ignore attribute) for testing the whole functionality (no mocks) manually. See the next section about
Mocks and
how to add fast tests without having to wait for external dependencies or slow code via mocks.
Native classes from external frameworks should not be derived to reduce dependencies (Windows Form, Graphics Device, Sound, native frameworks), always try to build a wrapper class around it! Otherwise we have a dependency we cannot test (tests would create forms, graphics, sockets, framework classes, etc. which is slow and sometimes not even automatically testable). This even goes beyond external frameworks, but also includes .NET framework classes providing external functionality like file handling, databases, xml.
All public methods should have tests (you probably need more for 100% coverage, or just remove unused methods). Functional visual tests (rendering, multimedia, input) are in classes derived from TestWithMocksOrVisually and accessible through the Editor.
A good stategy is:
- Write automated test first.
- Sometimes you need visual feedback, use TestWithMocksOrVisually.
- After your task is completed, make sure each test runs below 10ms, otherwise put them to the integration tests [Category("Slow")] which are excluded from NCrunch but run nightly instead.
Note: Obviously write tests first and hold them to the same standard as production code. This also makes following the guidelines easier because you will most likely not write clean code at the first attempt. Instead you need to refactor it a lot and make it more beautiful as you go on. You will need unit tests that are executed whenever the project compiles to stay safe and be aware of all issues your refactoring might have brought in.
Mocking is not easy, especially not for beginners and if the code is not written in a test driven manner or if the code comes from an outside framework that is generally hard to test. It is often misunderstood and not liked by many (even professional) programmers because it ads lots of extra code and overhead to your code base without any visible benefits to production code. Mocking is however crucial to testing and the code that is required to make code mock-able is usually required to get rid of dependencies anyway and will make the code much more stable, robust and easier to change because of tests that run all the time (via NCrunch). Visual and functional tests are great to verify UI and graphical behaviour, but you cannot run all visual tests in a project for every little change. You can however let NCrunch run all the tests all the time in the background! The most important aspect of mocking is making slow and external code run fast by just not executing it at all. Also only with Mocks it is possible to accomplish 100% automated code coverage (except for stuff that is mocked away like external frameworks and slow code, which we usually test with automated integration tests that run nightly).
Most mocks are just stubs or fake objects to be able to test quickly without invoking slow framework methods (IO, GPU, API calls). Some times a spy is also useful to inspect inner data. For this reason mock, fake and spy classes can break some rules defined below for easy access (public static). For details see
http://en.wikipedia.org/wiki/Test_double
So mocking excludes functionality (external code, slow code, i/o code, etc.), which has the advantage to quickly test if your code is doing what it is supposed to do, but this obviously does not test if the code makes sense at all or even works correctly with external frameworks or IO. Many things can still be tested automatically via Integration tests (done in our engine via
[Test, Category("Slow")]), which just run the slow code (on demand and also automatically on our build server).
This still does not mean the code make sense, we just made sure every line of code is executed and does not crash and all the results are as expected. This does however not test if there was something visible on the screen, if the application makes sense or if a game is fun. Especially for UI, Graphics, Behaviour and testing your application functionality from the user perspective functional tests are required, which is usually just the Test class duplicated again, but using an Ignore attribute. These tests are not automatically executed by NCrunch, TestDriven.NET, NUnit or Resharpers Test Runners, but if you execute them manually one by one you can still execute them and all mocking from the engine will be automatically disabled so you see the real behavior.
You will see many times in the engine and sample games test projects that the same code is executed with and without mocks. This way we can reach 100% test coverage and make sure every single line of code is really executed both automatically and when actually running the application.
[Test]
public void StartProgramWithMocks()
{
Program.Main();
}
[Test, Ignore("Visual")]
public void StartProgramNormallyAsVisualTest()
{
Program.Main();
}
You can
download any source code file to understand our Coding Style. Please always follow the existing convention.
Here is a full example file. The following example has extra documentation because the functionality cannot be made 100% clear with just the name.
/// <summary>
/// Converts positions into easy to use 0-360 degrees (top=0, right=90, bottom=180, left=270).
/// </summary>
public float GetClockwiseUnitRotation(Point unitCirclePosition)
{
if (unitCirclePosition == Zero)
return 0;
float rawAngle = MathHelper.Atan(unitCirclePosition.X, -unitCirclePosition.Y);
return rawAngle < 0.0f ? 360.0f + rawAngle : rawAngle;
}
Each file should contain only one type, no matter how small the class, struct or enum is. Also no nested types are allowed except for exception classes or internal structures not needed outside (e.g. for native code), which are easy to exclude or ignore.
Each line should be a short as possible (around 40 characters), long lines can have 80-100 characters. 98 is the setting for wrapping long lines with ReSharper (because it does not count tabs as 2 characters). 101 is where our guideline is shown, use the
Editor Guidelines package! All of our tabs have this width and we have horizontal scrolling disabled. On smaller monitors this fits okay, on big monitors (1920px width upwards) two tabs fit nicely.
The following structure is used in all files:
/// <summary>
/// Description what the class is designed for (in general) and how it works and how the class is implemented (concretely).
/// Be precise and try to do it in one line, max. 3 lines! Only describe, do not repeat obvious names or functionality.
/// </summary>
public class PizzaFactory
{
// Always start with the constructor or first method that will be called (driven by TDD).
// Then list all the fields and helper methods for that method recursively until done.
// Then continue with the next public method (again driven by writing tests first).
}
Avoid public constants, try to use them only in your assembly (only Pi is allowed because it never changes). For details see
this and
this.
Tests are always in a separate .Tests project that can be excluded easily.
public class PizzaFactoryTests
{
// Unit tests use the [Test] attribute, integration test use [Test, Category("Slow")] and
// visual and functional tests are marked with [Test, Ignore] and have to be executed manually.
}
Projects should always use the .NET 4.0 Client Profile (x86) to make sure they run on as many systems as possible. Create new projects with the Delta Engine Empty Library if possible, this helps avoiding to fix build settings, target platforms and removing unneeded dependencies (not correct in your newly created projects).
When adding References outside DeltaEngine references you can use the typical Alt+Enter "Add Reference" shortcut with ReSharper 8. If you are still using ReSharper 7 or below, always use 'Manage NuGet Dependencies' to add nuget references to correctly update the nuget packages.config file.
Non-nuget and non-DeltaEngine assemblies are not allowed in the public repository (this keeps the source code download and repo really small).
New projects created should be put into correct folder structure:
- \DeltaEngine\Graphics\DeltaEngine.Graphics.csproj
- \DeltaEngine\Graphics\SharpDX\DeltaEngine.Graphics.SharpDX.csproj
- \DeltaEngine\Graphics\Tests\DeltaEngine.Graphics.Tests.csproj
Basically create the project by the last name part (e.g. Graphics, SharpDX or Tests) and put it in the folder one level above it (Graphics goes in DeltaEngine, Tests goes in Graphics, etc.). Then a new folder is created with the correct folder structure for the .csproj file. Finally rename the short project name to the full name and don't forget to rename the assembly name and namespace in the project properties as well to fit this full name:
DeltaEngine.Graphics.SharpDX.csproj
Do not write static helper classes, handler or managers to avoid dependencies that cannot be undone. Static methods should only appear for Extension classes or private helper functions.
Use var when creating a type via the constructor. Then the variable type is already know (instance or value type are handled the same way). ReSharper will automatically suggest this:
var position = new Point(1, 2);
int number = 5;
string helloWorld = "Hello World";
string[] splittedHelloWorld = helloWorld.Split(new[] { ' ' });
var customTexts = new string[] { "Hi", "there" };
Negate if to make source code more clear:
public virtual void Dispose()
{
if (container == null)
return;
container.Dispose();
container = null;
}
Try to avoid false checks, if this is not possible always use the negate operator "!" instead of writing " == false", e.g.
if (!ContainsText("Hello"))
AddText("Hello");
AddText("Something else that is always added");
Use lambdas to simplify work, use the ReSharper code issue rules to guide you (single statements should always be lambdas and be fit into one line).
class Program
{
new Command(Command.Click, position => new Line2D(Point.Zero, position, Color.Teal));
}
Another good way to simplify code is to use ??, which is especially useful for caching:
public Generator Generator
{
get { return cachedGenerator ?? (cachedGenerator = GetNewGenerator()); }
}
All code should be indented with tabs using 2 spaces (in VS settings use smart tabs, 2 spaces per tab, keep tabs always). Horizontal scrolling is forbidden (see above, lines should be below 80 characters, max. 100).
Brackets should only be needed at method or property level, everything else should fit in one line. A blank line should be added after each logical block (ifs, fors, method groups). Exceptions are do-while loops, switch, try, catch and finally, which all require brackets by the language design, but still try to keep the code in them very compact (1 line if possible).
if (text == null)
return "no text";
for (int index = 0; index < text.Length; index++)
if (text[index] == '!')
return text.Substring(0, index);
Note: Checking for null should be avoided, unit tests should check for this not to happen. Sometimes it still is required for high level public engine methods because we don't know what users will pass in.
In order to keep code short and simple, try to use linq queries and extension methods when collections are iterated and something is returned (a boolean, a string or another collection). Do not do this for normal enumerations (foreach and calling a method with each instance) even though ReSharper will suggest it. Do not worry too much about performance, you can still revert this when profiling and remember that the Delta Engine services will automatically optimize all possibly slow code (including linq queries).
Instead of:
foreach (string name in names)
if (nameToCheck.StartsWith(name))
return true;
return false;
rewrite it with the Any extension method as ReSharper will suggest:
return names.Any(name => assemblyName.StartsWith(name));
Here is a more complex code block that can be replaced with an easier to understand linq query:
var runners = new List<:Runner>();
foreach (Type type in assembly.GetTypes())
if (IsNonAbstractRunner(type))
runners.Add(resolver(type) as Runner);
return runners;
Which can be turned into this simple linq query (notice the linq query formatting):
return
from type in assembly.GetTypes()
where IsNonAbstractRunner(type)
select resolver(type) as Runner;
But there are also examples of code that should not always be changed to linq as it does not get easier to read at all. Whenever the code gets longer by converting into a linq query, line lengths explode or when there is nothing to return and it reads nice as is, the code should usually stay as it is.
foreach (var type in types)
if (!type.IsAbstract)
AddType(type);
Constants and fields of classes or structs should always be kept to a bare minimum. A good number is 0-5 private fields, maybe 10-15 for complex classes, anything above that needs serious rethinking!
Every time you use a struct inside a class and need public access for modification to it (e.g. DrawArea, RotationCenter, Matrix, etc.), it makes sense to provide it as a public field /// <summary>
/// Keeps position, size and color for an automatically rendered rectangle on screen.
/// </summary>
public class ColoredRectangle : Renderable
{
public ColoredRectangle(Renderer render, Rectangle rect, Color color)
: base(render)
{
Rect = rect;
Color = color;
}
public ColoredRectangle(Renderer render, Point center, Size size, Color color)
: this(render, Rectangle.FromCenter(center, size), color) { }
public Rectangle Rect;
public Color Color;
protected internal override void Render()
{
render.SetColor(Color);
render.DrawRectangle(Rect);
}
}
All class instances are either created by the Resolver (inside App) or in code an then injected into other classes. The only exception is the Scene class because we always need access to content files, patterns and entities stores in the Content folder and managed via the Editor. Content is managed through a scene stack and we only work at the top most one, e.g. in games each level and the menu have their scenes. Scenes can use content files, patterns and entities from parent scenes as well.
There should be a maximum of 3 constructors (use default parameters and derived classes for extra functionality).
See file structure.
In case you need to assign to fields or properties, use
this to assign the constructor parameter value to the field:
public LogoApp(Image image)
{
this.image = image;
}
private Image image;
public void Update()
{
image.DrawArea = Movement * Time.Delta;
}
Public methods are also limited by 10 per class, private methods are not limited, but a class should be as short and concise as possible (around max. 200-400 lines). Each public method should be followed by the private methods that are called in it in the same order.
Each method should do one thing, and only have zero or one parameter. If required use two parameters, three should be the absolute limit. The same is true for constructor parameters, split up your functionality into several classes or use
Property Injection if you need access to more things. The only exception should be constructors of manager type classes (but don't name them manager or helper), for example it is perfectly okay for the Input class to get injected by all kind of devices because that is what this class is for. Doing one thing means:
- Methods called Get, Is, etc. should only grab data and not modify the instance at all! The should also not "do" anything except return existing values. Using simple calculations, ifs or dictionaries is okay.
- Other methods should do something with the instance based on the passed in parameters (e.g. Save(filename) will save out the data to disk, Update will update a game tick) and not return anything.
- Only Create or Load methods should actually create new instances and return them (e.g. Content.Load or BinaryFactory.Create)
- Keep methods short. One method should not do all initialization, all loading, all conditional logic and all rendering functionality, split it up into multiple methods and call them all in your method (then it still does the same thing, but nothing of the original "doing" is left, the method has a new purpose now to call multiple low level methods).
- Exception handling should be in its own method, it should only have one line to try something and the exception handler block (usually one line as well, max 3-5 lines per handler block, max 3handlers). See an example below.
Normally a method should only need 3-5 lines of code, the limit is 10 lines of code. The absolute limit is 20 lines and this should only be done in private methods and when you cannot avoid it (when switch statements or legacy code handling is required). Code reviews are essential to keep the code this simple! Comments are rarely needed for methods and it is usually a violation if you see any comment inside a method, especially for if statements or method calls. Try to incorporate the comment into the method call and simplify.
All preprocessor directives are to be avoided:
#if DEBUG
AddGridOutline();
#endif
As an alternative use
ExceptionExtensions.IsDebugMode
Enums should be short, names should be descriptive and often do not need extra comments. Enum values should be singular: Key.A, Key.Space
Properties should describe exactly what they do in the name and a xml comment should never be needed. If a property does more than just return a value or when it is not clear what it does, consider using a method instead. 10 is the maximum for public properties in any class (including public fields).
General rules from
Entity Component System should be applied (if not you will get an exception anyway). For example all
Component and
EntityContent classes just have properties, no functionality. Some rare components have a public static readonly Shared field because there is always only one instance (Inactive, RenderData, Gravity2D, GizmoEditMode). Also generated code (e.g. Content.cs) can have public static helper properties to simplify access to external resources in a statically typed safe way.
Sometimes it is useful to specify extra optional functionality in a class via a property like Logging, Randomizing, Access to other dependency injected classes, etc. If the class cannot do its job without the dependency, then add it to the constructor (not doing this would result in not fully initialized classes, which is never good). However sometimes you only rarely need certain functionality like Logging, which only is used if something goes wrong and you want to support both not logging at all (leave the Log property null) or to Log in the way you have setup somewhere else (e.g. to a log service, to a file, to the console for tests, etc.). The same could be said for a number of low level utility classes (Time, Random, File, IO, Networking, Profiling, Xml, Math, Json, Cryptography, etc.). Additional reading:
http://martinfowler.com/articles/injection.html#ConstructorVersusSetterInjection
If your class depends on something, make sure you inject the dependency in the constructor, otherwise you may use
Property Injection for optional functionality or things the class can resolve on its own. Here are some examples (also shows how to use exception handling in methods):
public Log Log { get; set; }
//...
public void ExecuteCrazyThing()
{
try
{
TryExecuteCrazyThing();
}
catch (CrazyException ex)
{
if (Log != null)
Log.Error(ex);
throw ex;
}
}
private void TryExecuteCrazyThing()
{
//...
}
All constants (including public static readonly, which is only allowed for value types), methods, public properties and public fields (usually of of struct types or when using [FieldOffset]) should be upper case:
public float Center
{
get { return TopLeft + Size / 2; }
set { TopLeft = value - Size / 2; }
}
public float X { get; set; }
public float Y { get; set; }
public const float Pi = 3.14159265359f;
public static readonly Point Zero = new Point(0, 0);
[FieldOffset(0)]
public Point Position;
public Rectangle DrawArea;
All other fields or properties should be lower case (it should be rare writing a private only property, protected properties might exist, but should be rare too):
protected Socket socket;
private Factory factory;
private Texture2D texture;
private SamplerState samplerState;
Only write them when really required, try to describe with method names if at all possible (comments should be avoided at all cost). The absolute limit is 2 lines of comments, no TODO, HACK, TOCO, etc. are allowed (all tasks have to be in our project management system, not in source code)! If you want to write more and detailed explanations create a FogBugz wiki page and link it up in the xml summary.
Also commented out code is not allowed when checking in. Check in your code often, so you can always revert. Also keep the code clean of any header (only leave legal notes in if required) and do not post any user comments, notes or time stamps. Keep the source code clean and professional.
Basically the best code you can write does not need any comments, except for the class summary you will find many classes in the Delta Engine that have no comments at all and explain what they do well in code already.
All public engine classes need an xml comment for the help generation! If at all possible stick Xml summary comments into one line (see examples above). The maximum amount is 3 lines, only add extra parameter information if you have something useful to say for them. Optionally public methods can have comments too, but it is not required. Everything in a source file should be as small as possible, leave out useless comments! For detailed documentation about high level concepts check this website and ask in the
Forum.
Regions are only allowed for hiding auto-generated code (rarely ever happens nowadays with non-windows-forms). There should be no reason to use regions, you can collapse methods directly and source files should be small anyway.
Reuse external libraries and code as much as possible. If it does not work, try a different library. If nothing works, reinvent the wheel :)
Reuse existing engine code, use what classes are for, use helper method, use existing delegates, etc.
This is also true if custom delegates can be avoided, e.g. use Action for simple delegates and Action<Type> for delegates with return values, e.g. instead of defining your own custom delegate for size changes just use an Action:
event Action<Size> SizeChanged;
Always collapse single line delegates into lambdas (see the example file from above) If you are unsure ask a fellow Delta Engine developer (or ask in the Forum if you are not part of the team).
This seems obvious but remember that an ‘Event’ is always something that has happened. It should therefore reflect this in it’s name being in the past tense. For more details about event streams, DDD and CQRS, check the wiki.
In tools, websites, client side code that end users can see always use Localization for strings. It is okay to have just English error messages if the target is a programmer anyway (e.g. an exception an end user should never see). Obviously try to use enums and just exception class names without messages internally and only present the error to the user or programmer at a higher level.
Always use string concatenation with the + operator. String.Format is not only harder to maintain, ugly and error prone, but also slower. It still might be a good choice for a variable params list, but that should be the only place were you should use String.Format. Here are a few hard to understand examples:
string.Format("sign /sha1 {0} \"{1}\"", this.CertificateThumbprint,this.XapOutputFile);
string signArgs = string.Format(
"-keystore {0} -signedjar \"{2}\" -storepass {1} -keypass {1} \"{3}\" {4}",
keystorePath, keypass, signedApk, packageFile, keyAlias);
This type of code should be avoided. Anything longer than a line of code (max. 100 characters) is already a smell. Instead write command line code into little helper methods to be reused many times.
Details about Entities, Triggers, Commands (all recommended to be build and setup via the
Editor) and how high level code (like Processors for Entities) should be written can be found in our wiki:
- To check if all code still follows the basic rules of clean code use NDepend, Resharper and NCrunch a lot! See our Build Server Results to see how we are doing.
- Prior to v0.9.6 we had detailed Coding Conventions for internal setup and it also answers a lots of detailed questions. It was however too long for most people.
- Additionally you can check out the Coding Style Links.