= !MapGuide Coding Standards = This document was originally based on a document maintained by Bruce Dechant, entitled !MapGuide Coding Standards and Guidelines, rev 1.6, last updated July 7, 2008. '''Revision History''' ||'''Revision'''||'''Date'''||'''Author'''||'''Comment'''|| ||1.6.4||19 Sep, 2022||Jackie Ng||Clarified expected C++ standards level, added some considerations on public API naming/design and more notes|| ||1.6.3||26 Jan, 2015||Jackie Ng||Added section about argument checking|| ||1.6.2||9 Dec, 2012||Jackie Ng||More about C++ documentation|| ||1.6.1||31 May, 2012||Jackie Ng||More about exception handling|| ||1.6||July 7, 2008||Bruce Dechant||Initial public revision|| [[PageOutline(2-4,Table of Contents,inline)]] == Introduction == This document describes the coding guidelines to be used for the !MapGuide project. Here are a few reasons why we need coding guidelines: * Most of the cost of software goes to maintenance * The maintenance is done by many different individuals over the lifetime of a given software product and not the original developer. * Guidelines improve readability of the software and help make the learning curve of new developers easier. == C++ Standards Version == MapGuide (and FDO) are expected to be built on a compiler that supports **C++11** Avoid using language constructs or standard library features that are introduced in C++ versions newer than this version Avoid using language constructs or standard library features that were introduced in C++ versions prior to this version, but have since been deprecated (eg. Usage of `std::auto_ptr` instead of `std::unique_ptr`) A good reference guide to know what constructs or library features are valid for any C++ version is [https://en.cppreference.com/ cppreference] == Consistency with .NET == Parts of !MapGuide are based on Microsoft's .NET framework, and therefore we should understand and follow the standards/design guidelines recommended by Microsoft for .NET. In Visual Studio .NET, you can find these under Design Guidelines for Class Library Developers. Alternatively, you can go to the help index and navigate to: {{{ Visual Studio .NET .NET Framework Reference Design Guidelines for Class Library Developers }}} The guidelines discuss topics such as naming conventions, how to decide whether something should be a property or method, proper use of enumerations, delegates, and events, and lots of other things. You should become generally familiar with these guidelines. == Class Names == All class names should be prefixed with “Mg”. Examples: {{{ #!cpp class MgServerSelectFeatures class MG_SERVER_MAPPING_API MgMappingUtil class MG_SERVER_RENDERING_API MgServerRenderingService : public MgRenderingService }}} == Class Variables == Any variable that needs to be visible outside of a class will expose itself via accessors (either protected, public, or internal). In general we only use private member variables unless there is a very good reason for any other type. Consequently, variables may be accessed directly only inside the class where they are defined. All other access is done through the accessor. This will help to support multi-threading and distributed objects. == Documentation Standard == === .NET === When you define a new public class, property, or method, you must follow the XML Doc standard. Typing three slashes “///” just before a class/property/method declaration will bring up a skeleton XML Doc header in .NET. You then simply need to add information in the different fields. Note that if you fail to add the XML Doc header for .NET, the code may not compile (this is an option that we will enable for all our projects). XML Doc Standard example: {{{ #!cpp /// ///Summary of CanIgnoreElement. /// /// /// /// /// ///Description of parameter element /// /// ///Description of parameter fileName /// /// private bool CanIgnoreElement(Element element, string fileName) }}} === C++ === For C++, you can choose either to use the above .net XML doc standard or the [http://www.stack.nl/~dimitri/doxygen/manual.html doxygen ] standard. To facilitate the possibility of transferring such documentation fragments to wrapper proxy classes in the future, '''you should use the doxygen standard'''. If a given method is deprecated and should not be used in the future, please indicate so with the {{{\deprecated}}} doxygen directive. This will do two things: 1. Cause the method in question to appear under the list of deprecated methods (and appear as deprecated under the class method documentation) in the API documentation that's generated by doxygen 2. Allow for us in the future to convert these directives to {{{[ObsoleteAttribute]}}} in .net and {{{@Deprecated}}} in Java for their proxy classes. An example: {{{ /////////////////////////////////////////////////////// /// \brief /// Don't use me /// /// \deprecated void AMethodThatShouldNotBeUsedAnymore(); }}} For new APIs, indicate the milestone the API was introduced with the {{{\since}}} directive For example if the new API is available since 2.5: {{{ /////////////////////////////////////////////////////// /// \brief /// Some new method /// /// \since 2.5 void SomeNewMethod(); }}} === General === It’s important to stay disciplined to avoid getting into the habit of "coding now, documenting later". Whenever you add or modify any classes/properties/methods, you must provide/update the documentation as well. The documentation you write needs to be of sufficient quality so that someone other then you can work with it. We as developers are ultimately responsible for providing the technical information that is required to fully document a class/property/method – not someone else. We’ll refer to this documentation as "seed documentation", and its content should be as close as possible to the end-user reference documentation. Note that private variables/properties/methods should be documented as well (for code readability). In this case, however, simply use the normal C++ comment style, e.g. {{{ #!cpp // the shoe size, including half-sizes private float shoeSize }}} === Adding Documentation to Existing Code === Another useful habit to get into is to add documentation to existing code on the go where it's missing or needs enhancing. If you spend half an hour figuring out what happened in a code section you are calling or which has called your code, add some helpful comments as it will help you and others next time trying to understand it. It's also worthwhile to submit patches for files you were only adding inline documentation to, as it's less likely that the original developer(s) will need comments to understand the code and do this for you. === Copy and Paste vs. Method Extraction === Many times copy and paste is an easy way to make code work. The downside is that it inflates the code and there is a risk of obfuscating essential details. If some code is doing the same thing it's worthwhile trying to extract a helper method so that it can be reused. This makes the code much easier to read and understand. In the extreme case copy 'n paste leads to hundreds of lines of identical code with just a few lines of changes inside. This significantly increases the risk of introducing defects, since when anything changes later in time the code has to be changed in many places instead of one. == Public API design considerations == We use [https://swig.org/ SWIG] to generate wrappers to MapGuide's C++ public API surface in PHP, Java and .net. When introducing a new API to MapGuide, you should use the least-common-denominator subset of C++ language features/constructs to best facilitate wrapper generation across all 3 managed language targets, **even if this is not the most optimal method** in terms of C++ runtime performance. The following should be used for method parameters and return types: * Allowed method parameter types * Basic primitive types `bool/BYTE/INT16/INT32/INT64/float/double` * Values you would normally model and pass as `enum`s should instead be represented with one of the above numeric primitive types and you should also define a "constant" class with allowed values as static members. * Raw pointer to any public MapGuide API class * For strings, only `std::wstring` (aliased in MapGuide as `STRING`) is permitted and only by const reference (aliased in MapGuide as `CREFSTRING`) * For arrays/collections, use a raw pointer to any subclass of `MgCollection` * For byte buffers or binary content, use a raw pointer to `MgByteReader` * Allowed method return types * Basic primitive types `bool/BYTE/INT16/INT32/INT64/float/double` * Values you would normally model and return as `enum`s should instead be represented with one of the above numeric primitive types and you should also define a "constant" class with allowed values as static members. * Raw pointer to any public MapGuide API class * For strings, only `std::wstring` (aliased in MapGuide as `STRING`) is permitted and only by value * For byte buffers or binary content, return a raw pointer to `MgByteReader` * For arrays/collections, never return a raw pointer or a STL collection type. Always return a raw pointer to any subclass of `MgCollection` Do not bother/try making public API methods `const`-correct, it will not affect SWIG interface generation and may even complicate or break it in some cases. Do not express default values for any parameters in the method signature, SWIG may not reliably map such a concept for our 3 language targets, assume the caller needs to pass all the values for any given method Anything not on the above list is something assumed to not be reliably SWIG-wrappable with consistent behavior across our 3 managed language targets and should not be used. When naming methods, use a name that is unlikely to collide with reserved/built-in method names of any language target or methods that would be auto-generated by SWIG for given language target. For example: * `delete` is a bad name for a new public API method because this is the name of the method SWIG auto-generates for Java proxy classes * `finalize` is a bad name for a new public API method because this is the name of the built-in method in Java classes that will be called when an object is about to be garbage collected Avoid having too many parameters in your new public API method. If you are starting to have too many parameters in your method, this is a sign to introduce a new "options" class for that method instead. For private/internal methods, you are free to name and define method parameters and return type in any way you see fit for best C++ runtime performance. === A remark about overloaded methods === Avoid introducing overloaded methods or methods that may have overloaded variants in the future to the public API surface if possible. Using an "options" parameter class pattern can future-proof the need to introduce new overloads down the road as you can just introduce new methods on the "options" class itself and update your implementation to handle the new option class methods accordingly. If you have to use overloaded methods, do not introduce an overload to a virtual method whose new signature only exists on the derived class and not its base. This makes such a class difficult to wrap for our PHP language target and requires a language-specific SWIG `%rename` workaround. An example of where such a scheme made PHP class generation difficult is in the `GetDistance` method of `MgCoordinateSystemMeasure`. It's parent `MgMeasure` class defines `GetDistance` with this signature: {{{ class MgMeasure { public: ... virtual double GetDistance(MgCoordinate* coord1, MgCoordinate* coord2) = 0; }; }}} In `MgCoordinateSystemMeasure`, this `GetDistance` method exists in 2 overloaded forms {{{ class MgCoordinateSystemMeasure { public: ... virtual double GetDistance(MgCoordinate* coord1, MgCoordinate* coord2) = 0; virtual double GetDistance(double x1, double y1, double x2, double y2)=0; //New overload only in MgCoordinateSystemMeasure }; }}} When generated by SWIG as-is, the .net and Java proxy classes are correct, but the PHP proxy class for `MgCoordinateSystemMeasure` will cause the PHP binding to throw a PHP fatal error of the form: {{{ PHP Fatal error: Declaration of MgCoordinateSystemMeasure::GetDistance(MgCoordinate|float|null $arg1, MgCoordinate|float|null $arg2, float $arg3, float $arg4): float must be compatible with MgMeasure::GetDistance(?MgCoordinate $arg1, ?MgCoordinate $arg2): float in Unknown on line 0 }}} The remedy was to add a SWIG `%rename` directive to the PHP language binding configuration for this one particular method signature to rename the method to `GetDistanceSimple` avoiding the resulting method signature conflict error above. By avoiding introducing overloaded methods and preferring the use of an "options" class instead, we avoid this PHP-specific problem and ensure consistent SWIG wrapper generation across our supported language targets. == Object Creation and Runtime Speed == As with Java, unnecessary object creation is something you should avoid whenever possible. Just be smart about it when you code. * If a call to a method returns a new object and you call that method multiple times in the same context, then change your code to call this method only once and reuse the returned value. * Use private helper objects when it makes sense to avoid unnecessary object creation. When you do need to create objects, check if it's possible to create them on the stack rather than on the heap. Consider the following example: {{{ #!cpp Ptr spFoo = new MgFoo(arg); Bar(spFoo); }}} In this case !MgFoo is a ref-counted object, and because of this you might think your only choice is to call new and assign the result to a smart pointer. In fact, if the call to Bar does not add any references to the object then the following code which doesn't call new also works: {{{ #!cpp MgFoo foo(arg); Bar(&foo); }}} Of course the same stack vs. heap thinking applies to non-ref-counted objects. == Argument Checking and Validation == Where possible, avoid doing your own argument checking/validation and manually throwing exceptions for invalid method arguments and use one of the available macros to do this for you in {{{Foundation\System\Util.h}}} {{{ CHECKNULL(pointer, methodname) //Check that pointer is not null. Throws MgNullReferenceException if null CHECKARGUMENTNULL(pointer, methodname) //Check that pointer is not null. Throws MgNullArgumentException if null CHECKARGUMENTEMPTYSTRING(stlstring, methodname) //Check that given std::basic_string is not empty. Throws MgInvalidArgumentException if empty MG_CHECK_RANGE(value, min, max, methodname) //Check that given argument is within the specified range. Throws MgNullReferenceException if null }}} These macros will not only do the requisite checks, but also capture the name of the offending argument and use the correct error message == Threading Guidelines == It’s important to follow strict threading rules when writing new code. Some of the !MapGuide code will be used in a multithreaded deployment environment. Failing to follow multi-threading guidelines will be costly in the future. There are detailed guidelines provided by Microsoft's ''Design Guidelines for Class Library Developers'' ([ms-help://MS.VSCC/MS.MSDNVS/cpgenref/html/cpconnetframeworkdesignguidelines.htm local VS link]) that should be reviewed. Here are some points from those guidelines: * Avoid providing static methods that alter static state * Be aware of method calls in locked sections * Avoid the need for synchronization if possible * Avoid using global variables == Source File Naming == The naming of source code files will match the class names except that the “Mg” prefix will be dropped. For example, if I have a class called MgClassA then all source files for that class will have the name ClassA. If this class is defined in C#, then there is only one source file with the .cs extension. If this class is defined in C++, then there are two files: the source file with extension .cpp and the header file with extension .h. No mismatches between class names and file names are allowed! One consequence of this rule is that you can only define one class/enumeration/struct/interface per file. == Folder Hierarchy == Just as the file name reflects the class name, the folder hierarchy will reflect the namespace or module hierarchy. If MgClassA is defined in the namespace OSGeo.!MapGuide.!DataAccess, then relative to the project root all source files for MgClassA will reside in the OSGeo\!MapGuide\!DataAccess folder. You will see this hierarchy reflected in the source control application. Again, having the hierarchies match up makes it very easy to navigate to where you want to go. A consequence of this rule is that if you change a namespace or module then you will also have to rename or restructure the file hierarchy. == Deleting Files and Projects == Eventually everyone needs to remove files and/or projects from the main source. If you’re removing files, you update the Visual Studio solution file and if applicable the Linux build script. Having done this, you still need to remove the file / project from the source control application. == General Structure of !MapGuide Files == We would like all !MapGuide files to have a similar structure: 1. Copyright info * every source file must have this at the top * the actual text in the copyright is subject to change, but that doesn’t mean we shouldn’t add it * whenever you create a new file, find another file with the copyright and paste that into your new file 1. Import of .NET namespaces (If applicable) * ideally sorted alphabetically 1. Import of local namespaces (If applicable) * ideally sorted alphabetically 1. Namespace info (If applicable) * as mentioned above, the namespace matches the folder path of the source file(s) 1. Class declaration * includes seed documentation 1. Constant declarations 1. Local variable declarations 1. Inner classes separated by section comments (If applicable) 1. Constructors 1. Properties/methods For example: {{{ #!cpp // // Copyright (C) 2004-2008 by Autodesk, Inc. // // This library is free software; you can redistribute it and/or // modify it under the terms of version 2.1 of the GNU Lesser // General Public License as published by the Free Software Foundation. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // using System; using System.Runtime.Serialization; namespace OSGeo.MapGuide.DataAccess.Tools { /// /// Provides the base functionality for ... /// [Serializable] public class MyClass : ISerializable { //------------------------------------------------------- // Constants //------------------------------------------------------- Constants //------------------------------------------------------- // Variables //------------------------------------------------------- Variables //------------------------------------------------------- // Inner classes //------------------------------------------------------- Inner classes //------------------------------------------------------- // Constructors //------------------------------------------------------- Constructors //------------------------------------------------------- // Properties / Methods //------------------------------------------------------- Properties / Methods } } }}} == IDE Settings == It is recommended that you use Visual Studio 2008 for software development on Windows and it helps if some settings are the same for all users. Here are the ones you should make sure are set. Under Tools/Options/Text Editor/All Languages/Tabs, make sure the tab size is set to 4 and that the ‘Insert spaces’ option is turned on. The use of spaces everywhere rather than a mix of tabs and spaces has the following advantages: 1. It reduces the number of non-essential differences that show up when comparing files * If I have my editor settings set to use spaces and I edit an area that contains tabs, Visual Studio will sometimes replace the tabs with spaces 1. It prevents columns from lining up evenly * File comparison tools may treat the width of tabs & spaces unevenly (1 tab does not exactly equal 4 spaces) * Viewing a source file in Notepad is even worse: a tab is the equivalent of about 8 spaces == Stylistic Guidelines == === General === Use the next-line convention for curly braces: {{{ #!cpp public void Foo() { } }}} Another coding standard for C# is to preface all references to instance variables/properties/methods with the “this” keyword. For example: {{{ #!cpp public void Foo() { // update an instance member variable this.index = 0; // call an instance member function this.Foo(); } }}} From a stylistic viewpoint, this makes it very easy to distinguish which variables are member variables and which have temporary scope. In C++ this is typically done by prefixing names of member variables using “m_” and therefore the keyword “this” is not required. All static variables are named using Pascal casing according to the .NET standard. When referencing static constants or methods, always prefix it using the actual name of the class defining the constant or static method. For example: {{{ #!cpp public class MgFoo1 { /// /// The max iterations to use. /// public int maxIter; /// /// Default value for max iterations. /// static public int defaultMaxIterations = 10; ... } public class MgFoo2 extends MgFoo1 { ... this.maxIter = this.defaultMaxIterations; // wrong – not an instance variable this.maxIter = MgFoo2.defaultMaxIterations; // wrong - subclass doesn't declare it this.maxIter = MgFoo1.defaultMaxIterations; // correct ... } }}} === Compiler Warning === This guideline strongly encourages all compiler warnings to be treated as errors and therefore they should be fixed. === Strings === ==== Internally ==== For the !MapGuide project the internal string format to use will be wstring. For .NET development the String class will be used. ==== Across Wire ==== The UTF-16 standard will be used for transmitting strings across the wire between any of the !MapGuide products (client, web tier, and server). ==== Repository ==== The resource repository used by !MapGuide will use the UTF-8 standard for storing strings. ==== API Boundary ==== !MapGuide will use the following string format for API boundaries in the web tier and the server: {{{ #!cpp const wstring& }}} === Break and Goto === Goto and break make the code very hard to read. The guideline is to avoid using break statements inside loops and to use goto sparingly (if at all). === If, Else-If, Else Statements === The if-else statements should have the following form: {{{ #!cpp if (condition) { statements; } if (condition) { statements; } else { statements; } if (condition) { statements; } else if (condition) { statements; } else { statements; } }}} === For Statements === A for statement should have the following form: {{{ #!cpp for (initialization; condition; update) { statements; } }}} An empty for statement {{{ #!cpp for (initialization; condition; update); }}} Variable declaration inside for statement should NOT be done. Don’t do this: {{{ #!cpp for (int i=0; i<10; i++) { statements; } }}} Do this instead: {{{ #!cpp int i=0; for (i=0; i<10; i++) { statements; } }}} === !ForEach Statements === A foreach statement should have the following form: {{{ #!cpp foreach (var in col) { statements; } }}} === While Statements === A while statement should have the following form: {{{ #!cpp while (condition) { statements; } }}} An empty while statement should have the following form: {{{ #!cpp while (condition); }}} === Do-While Statements === A do-while statement should have the following form: {{{ #!cpp do { statements; } while (condition); }}} === Switch Statements === A switch statement should have the following form. C# requires a terminating break or goto for each case. {{{ #!cpp switch (condition) { case XYZ: statements; break; case ZYX: statements; break; default: statements; break; } }}} Every switch statement should include a default case. === Try-Catch Statements === A try-catch statement should have the following format: {{{ #!cpp try { statements; } catch (MgExceptionClass1 e) { statements; } catch (MgExceptionClass2 e) { statements; } }}} A try-catch statement may also be followed by `finally` which is always executed. {{{ #!cpp try { statements; } catch (MgExceptionClass1 e) { statements; } catch (MgExceptionClass2 e) { statements; } finally { statements; } }}} === Return Value === The use of a single return within a method or function is encouraged. The following structure: {{{ #!cpp if (booleanExpression) { return true; } else { return false; } }}} should be: {{{ #!cpp return booleanExpression; }}} Also, {{{ #!cpp if (condition) { return x; } return y; }}} should be: {{{ #!cpp return (condition ? x : y); }}} === Expressions With == === The following order should be used because it will help catch the accidental use of "=" when "==" was intended at compile time: {{{ #!cpp if (constant == variable) { // do something } else { // do something else } }}} === C++11 language constructs === As C++11 is the base language standard that the MapGuide/FDO codebase is expected to be compiled in, you are actively encouraged to take advantage of any language features/constructs introduced with this particular version of C++. As MapGuide/FDO was originally conceived before the introduction of C++11, there will be vast places in the codebase where code is written in the pre-C++11 style. You are encouraged (but not required) to migrate such existing code to the C++11 style if you encounter/touch it in your journey of fixing a bug or implementing a new feature. 2 key constructs C++11 constructs worthy of special consideration are detailed below ==== `auto` ==== Use `auto` where possible to let the C++ compiler infer the type of a local variable based on the type of the right-hand side of a variable assignment. This is most useful when dealing with STL iterators or sufficiently complex templated type where you can avoid having to specify the type like this: {{{ std::vector::iterator it = some_int_vector.begin(); }}} and just declare it like this: {{{ auto it = some_int_vector.begin(); }}} `auto` also has useful applications for primitive variable declarations as it can fix up to one half of any potential `data type mismatch` warnings as a result of declaring the variable with an incorrect (or insufficiently wide/narrow) type on the left-hand side of a variable assignment (think `size_t`/`int` indexing mismatches in for loops for an example) However, do not use `auto` as the return type (aka. return type deduction) in any public API method signature. We need to be as verbose and concise as possible for SWIG to generate the correct proxy classes and their methods. ==== for loops ==== When working with STL iterators it is much more preferred to use the simplified range-based for loops introduced in C++11 Pre C++11 loops look like this: {{{ for (some_type::iterator it = container.begin(); it != container.end(); it++) { some_item_type item = *it; ... } }}} Such loops can be simplified to this: {{{ for (auto const & item : container) { ... } }}} With adjustments to whether the item needs to be `const` or by reference depending on the mutability needs of the iterated item. == Pointers == === Normal Pointers === A pointer should always be checked before using. You should never assume that the pointer will be valid. This is good coding style and should be done. {{{ #!cpp if (NULL != data) { int size = data->GetSize(); ... } }}} This is a bad coding style and should not be done. {{{ #!cpp int size = data->GetSize(); }}} When you are no longer using a pointer you should set it to NULL so that it is clear that it is no longer valid. {{{ #!cpp ... delete pData; pData = NULL; }}} === Smart Pointers === ==== Our Code ==== The !MapGuide Ptr template class acts as a smart pointer and should be used whenever possible. Example: {{{ #!cpp MgRepository* MgServerResourceService::CreateRepository(CREFSTRING repositoryType, CREFSTRING repositoryName) { Ptr repository; MG_RESOURCE_SERVICE_TRY() if (MgRepositoryType::Library == repositoryType) { repository = new MgLibraryRepository(repositoryName, this); } else if (MgRepositoryType::Session == repositoryType) { repository = new MgSessionRepository(repositoryName, this); } else { throw new MgInvalidRepositoryTypeException(__LINE__, __WFILE__); } MG_RESOURCE_SERVICE_CATCH_AND_THROW() return repository.Detach(); } }}} ==== 3rd Party Library ==== Wherever possible the standard C++ smart pointer should be used when dealing with 3rd party libraries. The following is some sample code that uses the standard C++ smart pointer along with a typical !MapGuide Exception handling mechanism: {{{ #!cpp Bar* Foo::CreateBar() { unique_ptr bar; MG_TRY() bar = unique_ptr(new Bar); assert(0 != bar.get()); bar.DoSomething(); // might throw an exception MG_CATCH_AND_THROW() return bar.release(); // release ownership of the smart pointer } }}} Some smart pointer notes: 1. Don't use `auto_ptr`. This is deprecated in C++11 and removed in C++17 onwards. Most/all cases where you'd think to use `auto_ptr` you can use `unique_ptr` instead 2. If using `unique_ptr`, you may be tempted to use `std::make_unique`, but this is a C++14 standard library feature and we require code to be compiled in C++11 standards mode. If/when MapGuide/FDO adopts C++14 or higher as the base language standard version, usage of this API will be permitted. The following code is a cleaned up version of the above sample code using some helper macros. {{{ #!cpp Bar* Foo::CreateBar() { char* bar = 0; MG_TRY() bar = new char[256]; assert(0 != bar); DoSomething(bar); MG_CATCH() if (NULL != mgException) { delete[] bar; (*mgException).AddRef(); mgException->Raise(); } return bar; } }}} Note that MG_TRY(), MG_CATCH(), MG_THROW, MG_CATCH_AND_THROW and MG_CATCH_AND_RELEASE() macros can be anything specific to a !MapGuide component. Example macro definitions for the resource service: {{{ #!cpp #define MG_RESOURCE_SERVICE_TRY() \ MG_TRY() \ #define MG_RESOURCE_SERVICE_CATCH(methodName) \ } \ catch (XmlException& e) \ { \ MgStringCollection arguments; \ STRING message; \ \ if (DB_LOCK_DEADLOCK == e.getDbErrno()) \ { \ message = MgUtil::GetResourceMessage( \ MgResources::ResourceService, L"MgRepositoryBusy"); \ } \ else \ { \ MgUtil::MultiByteToWideChar(string(e.what()), message); \ } \ \ arguments.Add(message); \ mgException = new MgDbXmlException(methodName, __LINE__, __WFILE__, NULL, L"MgFormatInnerExceptionMessage", &arguments); \ (static_cast(mgException.p))->SetErrorCode(e.getDbErrno()); \ } \ catch (DbException& e) \ { \ MgStringCollection arguments; \ STRING message; \ \ if (DB_LOCK_DEADLOCK == e.get_errno()) \ { \ message = MgUtil::GetResourceMessage( \ MgResources::ResourceService, L"MgRepositoryBusy"); \ } \ else \ { \ MgUtil::MultiByteToWideChar(string(e.what()), message); \ } \ \ arguments.Add(message); \ mgException = new MgDbException(methodName, __LINE__, __WFILE__, NULL, L"MgFormatInnerExceptionMessage", &arguments); \ (static_cast(mgException.p))->SetErrorCode(e.get_errno()); \ } \ catch (DWFException& e) \ { \ MgStringCollection arguments; \ arguments.Add(STRING(e.message())); \ mgException = new MgDwfException(methodName, __LINE__, __WFILE__, NULL, L"MgFormatInnerExceptionMessage", &arguments); \ } \ catch (const XMLException& e) \ { \ MgStringCollection arguments; \ arguments.Add(X2W(e.getMessage())); \ mgException = new MgXmlParserException(methodName, __LINE__, __WFILE__, NULL, L"MgFormatInnerExceptionMessage", &arguments); \ } \ catch (const DOMException& e) \ { \ MgStringCollection arguments; \ arguments.Add(X2W(e.msg)); \ mgException = new MgXmlParserException(methodName, __LINE__, __WFILE__, NULL, L"MgFormatInnerExceptionMessage", &arguments); \ \ MG_CATCH(methodName) \ #define MG_RESOURCE_SERVICE_THROW() \ MG_THROW() \ #define MG_RESOURCE_SERVICE_CATCH_AND_THROW(methodName) \ MG_RESOURCE_SERVICE_CATCH(methodName) \ \ MG_RESOURCE_SERVICE_THROW() \ }}} == Exception Handling == MapGuide employs a series of macros to simplify exception handling, propagation and stack trace recording. The macros are explained below: * MG_TRY() - Denotes the start of a try/catch block. Also declares a local variable {{{mgException}}} of type {{{Ptr}}}. If any exception is caught, it will be assigned to this variable. * MG_CATCH(methodName) - Denotes the end of a try/catch block. This macro defines catch blocks for common exception types ({{{MgException}}}, {{{std::exception}}} and {{{...}}}) and will append the current method name to the exception's call stack for any caught {{{MgExceptions}}}. You can do additional processing on the caught exception through the {{{mgException}}} local variable * MG_THROW() - Re-throws the caught exception. In order the preserve stack trace information on {{{MgException}}} objects, you have to re-throw caught exceptions, which this macro will do. * MG_CATCH_AND_THROW() - Is a combination of MG_CATCH() followed by MG_THROW(). Generally used when you don't need to do any custom processing on the caught {{{MgException}}} and generally just used to record the current method on the stack trace before throwing it back up. Each service API has service-specific variations of the above macros to do service-specific exception processing. It is generally advised to use these service-specific variations when dealing with code for that particular service (eg. Use MG_FEATURE_SERVICE_TRY(), MG_FEATURE_SERVICE_CATCH(), etc when working inside Feature Service code) Avoid the use of raw try/catch statements where possible, especially if you expect to catch exceptions of type {{{MgException}}}. == Enumerations == Please use the following format when declaring enumerations. Example 1: {{{ #!cpp enum MgDimensionality { mdXY = 0, /// XY mdXYM, /// XY + Measure mdXYZ, /// XYZ mdXYZM, /// XYZ + Measure }; }}} Example 2: {{{ #!cpp enum MgConnectionState { mcsOpen = 0, /// Connection is still open mcsClose, /// Connection has been closed mcsBusy /// Connection is busy processing other request }; }}} Notice that an abbreviated lower-case form of the enumeration name is prepended to each item in the enumerator list. Per the public API design considerations section above, enums are never returned or accepted as parameters in any public MapGuide API, so the naming/formatting considerations here only apply for enums you define for any internal/private code where they will be used. == Parentheses == Even if the operator precedence seems clear to you, it is best to use parentheses to avoid any confusion by others. {{{ #!cpp if (a == b && c == d) // Bad if ((a == b) && (c == d)) // Good }}} == Assert == === .NET === Use Debug.Assert ( condition ) to do assertions. Do not use Trace.Assert as that will not be removed for release build. For C++ .NET projects you must do the following or else the Debug calls will still be called in release builds. {{{ #!cpp #ifdef _DEBUG Debug::WriteLine(SrsErrorMessage(status)); #endif }}} == Globalization == === .NET === All strings which need to be localized must be placed into resx resource files. === C++ === All strings which need to be localized must be placed into res resource files. The main string bundle resides in `Common/MapGuideCommon/Resources/mapguide_en.res` === General === Strings which do not need to be localized can remain in the code. However, you should indicate the string is non-localizable by adding a `NOXLATE` comment, e.g. {{{ #!cpp if (filename.EndsWith(".txt")) // NOXLATE { ... } }}} The comment indicates that the author of the code explicitly wants to exclude the string from being localized and also facilitates detection of un-localized strings. Any matches for string literal searches without the instance of the `NOXLATE` comment on that line is a strong hint that such a string is meant to be localized. Exception string messages should be globalized if possible. Debugging/debug-only messages do not require localization and is assumed to be in English. == Special Comments == Use BOGUS in a comment to flag something that is bogus but works. Use FIXME to flag something that is bogus and broken. Use TODO when something needs to be completed but working. Code that is not implemented or not tested should raise an exception. For every instance add necessary comment to further explain the situation.