Hoppa till innehåll

Skriva bättre kommentarer i C# och C++

↑

En bra kodkommentar förklarar sammanhang som inte framgår tydligt av själva koden. Det kan vara ett tekniskt krav, ett externt beroende, en ovanlig lösning eller kontraktet för ett publikt API. Kommentarer gör inte felaktig kod korrekt. Om varje rad upprepas i ord blir logiken ofta svårare att läsa.

Tre sätt att skriva kommentarer i C# och C++

Section titled “Tre sätt att skriva kommentarer i C# och C++”
Typ C# C++ Vanlig användning
En rad // förklaring // förklaring Kort motivering eller påminnelse
Flera rader / block /* förklaring */ /* förklaring */ Längre förklaring nära koden
Dokumentation XML som /// <summary>...</summary> Doxygen-kommentarer som /// @brief ... API-dokumentation och IDE-hjälp

Växla mellan C#- och C++-flikarna genom hela sidan. Exempel med fristående satser förutsätter att den omgivande metoden tillhandahåller variabler som score och serverLeaseMilliseconds. Klass-/funktionsexempel innehåller egna deklarationer. C++-exemplen använder C++20.

C# | Comments.cs
// En kommentar på en rad.
int retries = 3;
/* Den externa tjänsten kan tillfälligt returnera gamla data.
Därför försöker vi igen bara vid det kända problemet. */
int retryDelayMilliseconds = 250;
/// <summary>Representerar en spelares visningsnamn.</summary>
public sealed class PlayerName
{
public string Value { get; }
public PlayerName(string value) => Value = value;
}

I båda språken gäller // till radens slut och /* ... */ fram till avslutande markering; blockkommentarer kan inte nästlas. Kommentarer körs inte. C# ger /// XML-betydelse när kompilatorn genererar dokumentation. I C++ är /// fortfarande en radkommentar och ett separat verktyg som Doxygen tolkar dokumentationskonventionerna. Se Doxygens guide till dokumentationsblock.

Kommentarer som bara upprepar koden tillför lite:

C# | Undvik.cs
// Öka poängen med ett.
score++;
// Kontrollera om poängen är större än 100.
if (score > 100) { /* ... */ }

En bättre kommentar beskriver orsaken till ett beslut:

C# | ScoreService.cs
// Poäng över 100 följer med till nästa omgång.
// Begränsa bara värdet som visas i progressindikatorn.
int displayedScore = Math.Min(score, 100);

Fråga dig: Skulle en framtida utvecklare förlora viktig information om kommentaren togs bort? Om inte är ett tydligare namn ofta bättre.

Dokumentera syfte, indata, resultat och undantag vid API-deklarationen. C#-fliken använder XML-dokumentation och C++-fliken Doxygen-kommandon. De visar jämförbara kontrakt med olika samlingstyper: C# avvisar null eller tom lista, medan C++ tar en giltig std::span och avvisar en tom span. Båda hittar högsta värdet utan att ändra samlingen, även när alla poäng är negativa.

I C# beskriver summary, param, returns och exception kontraktet; see och paramref hänvisar till kodmedlemmar. Kompilatorn kontrollerar vissa referenser och parameternamn. I C++ behandlar Doxygen @brief, @param, @return och @throws; C++-kompilatorn verifierar inte texten och tvingar inte fram dokumenterade undantag.

C# | ScoreCalculator.cs
using System;
using System.Collections.Generic;
public static class ScoreCalculator
{
/// <summary>Hittar högsta poängen i en samling som inte är tom.</summary>
/// <param name="scores">Poäng att jämföra. Måste innehålla minst ett värde.</param>
/// <returns>Det största poängvärdet.</returns>
/// <exception cref="ArgumentNullException">
/// Kastas om <paramref name="scores" /> är null.
/// </exception>
/// <exception cref="ArgumentException">
/// Kastas om <paramref name="scores" /> är tom.
/// </exception>
/// <remarks>Samlingen ändras inte av metoden.</remarks>
/// <seealso cref="Math.Max(int, int)" />
public static int Highest(IReadOnlyList<int> scores)
{
ArgumentNullException.ThrowIfNull(scores);
if (scores.Count == 0)
throw new ArgumentException("Ange minst en poäng.", nameof(scores));
int highest = scores[0];
foreach (int score in scores)
highest = Math.Max(highest, score);
return highest;
}
}
C# XML C++ / Doxygen Funktion
<summary> @brief Kort beskrivning, ofta i IDE:ns hjälptext
<param name="..."> @param name Parameter och eventuella begränsningar
<returns> @return Förklaring av returvärdet
<remarks> @note / @details Kompletterande information och krav
<exception cref="..."> @throws ExceptionType När ett undantag kastas
<see cref="..."/> / <seealso cref="..."/> @ref / @see Referenser till andra kodmedlemmar
<typeparam name="..."> @tparam name Generisk parameter eller template-parameter
<example> @code ... @endcode Inbäddat användningsexempel

Generera dokumentation för respektive språk

Section titled “Generera dokumentation för respektive språk”

Lägg C#-inställningen i din befintliga .NET-projektfil. För C++ sparar du API-exemplet som score_calculator.cpp bredvid Doxyfile. Doxygen är ett separat verktyg som måste installeras; C++-kompilatorn genererar inte HTML-sidorna på egen hand.

Example.csproj: lägg inuti Project
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Med .NET SDK installerat kör du dotnet build Example.csproj från projektmappen. Exemplets ThrowIfNull kräver .NET 6 eller senare. Granska genererad XML bredvid assemblyn och eventuella dokumentationsvarningar från kompilatorn.

Kommandona fungerar i PowerShell eller Git Bash på Windows 10/11 och Bash på Linux när motsvarande SDK/verktyg är installerat och finns på PATH. Konfiguration styr genererade filer men installerar inga verktyg. Se Doxygens konfigurationsreferens.

Markeringarna är teamkonventioner, inte C#- eller C++-nyckelord. Enas om betydelsen så att läsaren förstår om kommentaren beskriver planerat arbete, ett bekräftat fel eller en tillfällig kompromiss. //TODO är giltig kommentarsyntax, men föredra ett enhetligt och lättläst format som // TODO: ....

Markering Använd när Skriv i kommentaren Ta bort när
TODO Ett bestämt planerat arbete återstår Åtgärd, orsak eller omfattning och villkoret/datumet/releasen som gör arbetet nödvändigt Arbetet och kontrollerna är klara eller teamet uttryckligen avbryter uppgiften
BUG Du har identifierat felaktigt beteende Utlösande indata, faktiskt jämfört med förväntat beteende och ärendelänk Felet är rättat och ett regressionstest verifierar förväntat beteende
FIXME En känd reservlösning eller implementationskompromiss behöver ersättas Varför den finns, avsedd ersättning och ett verifierbart borttagningsvillkor Ersättningen är klar och speciallösningen säkert kan tas bort

En BUG kan också behöva en tillfällig reservlösning. Spåra felet och borttagningen separat om de har olika färdigvillkor. Prioritet hör hemma i ärendehanteraren. En TODO är inte automatiskt mindre brådskande än en FIXME, och en BUG-kommentar gör inte osäker kod lämplig för release.

Placera kommentaren direkt ovanför relevant metod, uttryck eller litet kodblock, med samma indrag som koden. Lägg testpåminnelser vid metoden som testas eller i motsvarande testfil enligt teamets konvention. Undvik en fristående lista överst i en stor fil.

Beskriv åtgärden eller det observerbara problemet i stället för // FIXME: fult. Lägg till ärende-ID för arbete som behöver ansvarig person, prioritering eller samordning. ID-numren nedan är påhittade; ersätt med verkliga projektreferenser. Behåll markering och beskrivning på första raden så att enkel textsökning hittar dem.

C# | A searchable, actionable marker
// FIXME: Ta bort v1-reservlösningen efter migrering och avslutat v1-stöd (SAVE-185).
// Behåll den så länge stödda sparfiler kan innehålla enbart legacyName.

Använd C# / C++-flikarna för samma placeringsexempel i båda språken. C#-metoderna placeras i en klass; C++20-exemplen är fristående funktioner med nödvändiga headers. BUG-exemplen innehåller avsiktligt felaktigt beteende för att visa var ett identifierat fel dokumenteras. Exemplen använder inga OS-specifika API:er och fungerar med lämpliga C#- eller C++20-verktygskedjor på Windows 10, Windows 11 och Linux.

Exempel 1: ett test för specialfall saknas. Metoden returnerar redan noll för en tom array, men testtäckning saknas. Placera påminnelsen ovanför metoden eller vid motsvarande tester.

C# | InventoryService.cs: missing test
// TODO: Skriv ett test som visar att tomt inventarie ger 0 föremål (INV-142).
public static int CountItems(int[] itemIds)
{
return itemIds.Length;
}

Hantera: lägg till ett regressionstest med en tom array, verifiera att det passerar och ta bort TODO i samma ändring. Behåll testet efter att påminnelsen tagits bort. Om testet visar ett faktiskt fel dokumenterar du även det som en BUG.

Exempel 2: funktionalitet behövs inför en framtida funktion. Formateringen är för närvarande avsiktligt invariant. Lokaliserad formatering planeras innan en språkväljare släpps.

C# | InventoryLabels.cs: planned localization
// TODO: Formatera med vald UI-kultur innan språkväljaren släpps (UI-216).
public static string FormatItemCount(int count)
{
return count.ToString(System.Globalization.CultureInfo.InvariantCulture);
}

Hantera: länka UI-ärendet, implementera överenskommen kulturanpassad formatering och testa de kulturer som stöds. Ta bort TODO när kravet är uppfyllt. Om språkväljaren redan har släppts och beteendet bryter dess kontrakt ska det behandlas som ett fel, inte som valfritt framtida arbete.

BUG: beskriv felaktigt beteende och hur det återskapas

Section titled “BUG: beskriv felaktigt beteende och hur det återskapas”

Exempel 1: en äldre poäng konverteras till ett felaktigt värde utan varning. Placera markeringen direkt ovanför konverteringen som orsakar felet.

C# | SaveReader.cs: deliberately faulty conversion
public static int ReadScore(long savedScore)
{
// BUG: 2147483648 blir negativt; avvisa poäng utanför tillåtet intervall (SAVE-184).
return unchecked((int)savedScore);
}

Hantera: återskapa med 2147483648L i C# eller 2147483648LL i C++, skriv ett regressionstest som misslyckas och validera därefter tillåtet intervall med överenskommet avvisningsbeteende. Testa giltiga gränsvärden och värden utanför intervallet. Ta bort BUG när rättad implementation och tester levereras tillsammans. Om riktiga sparfiler påverkas spåras även datareparation och releaseprioritet i ärendet.

C++-exemplet använder std::int32_t och std::int64_t för uttryckliga bitbredder. Konverteringen utanför intervallet följer C++20-reglerna; anta inte samma resultat med godtycklig int-bredd eller äldre språkläge. Se C++-referensen för heltalskonverteringar.

Exempel 2: en sammanställning kraschar för tomt inventarie. Ange både utlösande villkor och förväntat resultat.

C# | InventorySummary.cs: deliberately faulty empty case
public static decimal AveragePrice(decimal totalPrice, int itemCount)
{
// BUG: itemCount == 0 kastar undantag; kontraktet för tomt inventarie kräver 0 (INV-143).
return totalPrice / itemCount;
}

Språkskillnad: C# använder decimal, vars division med noll kastar undantag. C++ saknar motsvarande inbyggda decimaltyp här, så exemplet använder double och kastar uttryckligen för tomt inventarie. Undantaget är det dokumenterade felet eftersom kontraktet kräver noll. Exemplet förlitar sig alltså inte på flyttalsdivision med noll eller odefinierad heltalsdivision med noll. Välj lämplig pengarepresentation för ett riktigt prissystem.

Hantera: skriv ett test för AveragePrice(0m, 0) i C# eller AveragePrice(0.0, 0) i C++, implementera det specificerade tomfallet och testa också ett normalt icke-tomt fall. Ta bort markeringen när båda passerar. Byt inte bara namn från BUG till TODO medan kraschen finns kvar.

FIXME: förklara kompromissen och villkoret för att ta bort den

Section titled “FIXME: förklara kompromissen och villkoret för att ta bort den”

Exempel 1: en reservlösning behövs fortfarande under migreringen. Koden fungerar för närvarande som avsett för äldre sparfiler som stöds. Det återstående arbetet är att ta bort kompatibilitetsvägen när den inte längre behövs.

C# | SaveMigration.cs: temporary compatibility
public static string ReadDisplayName(string displayName, string legacyName)
{
// FIXME: Ta bort legacyName-reservlösningen efter migrering och avslutat v1-stöd (SAVE-185).
// Stödda v1-sparfiler kan fortfarande sakna displayName.
return string.IsNullOrWhiteSpace(displayName) ? legacyName : displayName;
}

Språkskillnad: C++-exemplet tar giltiga string views och räknar tom sträng eller angivna ASCII-blanktecken som saknat namn. C# accepterar även null och identifierar fler Unicode-blanktecken genom IsNullOrWhiteSpace. Om sparfilerna behöver identiskt Unicode-beteende i båda språken krävs ett gemensamt normaliseringskontrakt och lämpligt Unicode-stöd; anta inte att kontrollerna är likvärdiga.

Hantera: behåll reservlösning och kompatibilitetstester så länge v1 stöds. När migreringen är verifierad och produkten inte längre stöder v1-sparfiler tar du bort reservlösningen, uppdaterar tester för stödda format och tar bort FIXME tillsammans. En sammanslagen migrerings-PR bevisar inte i sig att villkoret är uppfyllt.

Exempel 2: en extern tjänst skickar en avvikande språkidentifierare. Applikationen anpassar tillfälligt ett känt leverantörssvar.

C# | LocaleAdapter.cs: provider workaround
public static string NormalizeLocale(string locale)
{
// FIXME: Ta bort aliaset när alla stödda leverantörsversioner skickar sv-SE (LOC-231).
// Äldre svar använder sv_SE, som vår språkuppslagning avvisar.
return locale == "sv_SE" ? "sv-SE" : locale;
}

Hantera: länka leverantörens felärende och verifiera det rättade svaret i driftsatta versioner som stöds. Ta sedan bort aliaset och FIXME men behåll tester för det stödda indatakontraktet. Om äldre leverantörsversioner fortfarande måste stödjas kan anpassningen vara permanent kompatibilitetskod. Ersätt då FIXME med en vanlig förklaring i stället för att utlova en borttagning som inte kan ske.

  1. Dokumentera: välj markering, placera den vid berörd kod och formulera åtgärd eller fel konkret.
  2. Spåra: skapa/länka ett ärende för arbete som påverkar release, flera personer eller skjuts upp. Ange ansvarig person, prioritet och färdigvillkor där.
  3. Hitta: använd Riders TODO-fönster eller sök efter markeringarna i projektet.
  4. Åtgärda: ändra implementation eller tester, verifiera färdig-/borttagningsvillkoret och granska relaterade anropsställen.
  5. Städa: ta bort färdiga markeringar i samma PR/MR, stäng ärendet när samtliga kriterier är uppfyllda och behåll tester och varaktiga motiveringar.

På Windows 10, Windows 11 och Linux granskar du mönstren i Settings > Editor > TODO. Om FIXME saknas lägger du till exempelvis \b(?<TAG>FIXME):.*, sparar och verifierar // FIXME: ... i View > Tool Windows > TODO. Behåll versaler i BUG: för Riders skiftlägeskänsliga standardmönster; kortkommandon beror på tangentmappningen. Se JetBrains dokumentation om TODO-mönster.

Båda språken använder // och /* ... */. C# har inbyggd XML-dokumentation med ///; i C++ kan verktyg som Doxygen läsa egna dokumentationsformat.

C# | LeaseSettings.cs
// Förnya innan serverns tidsgräns.
const int RefreshSeconds = 45;

Använd ett bokmärke i Rider när du bara vill hitta tillbaka till en kodrad under felsökning. Ett bokmärke är inte en källkodskommentar. Kommentarer passar för information som ska följa koden, C# XML eller C++ Doxygen för publika API:er och ärenden för arbete som ska prioriteras och tilldelas.

  • Beskriva varje rad. Använd tydligare namn och mindre metoder i stället.
  • Kommentera bort gammal kod. Git sparar redan historiken; radera oanvänd kod.
  • Lämna gamla förklaringar. Uppdatera kommentaren när beteendet ändras.
  • Lova ett API-kontrakt som inte uppfylls. Beskrivning, implementation och tester måste stämma överens.
  • Skriva hemligheter i kommentarer. Lösenord, kunduppgifter och interna driftuppgifter hör inte hemma i källkodshistoriken.
  • Använda kommentarer i stället för validering. // måste vara positiv hindrar inte negativa indata.
  1. Läs metoden utan kommentarer. Är syftet redan tydligt?
  2. Behåll förklaringar av krav, invarianter, avvägningar och ovanliga beslut.
  3. Kontrollera att C# XML- eller C++ Doxygen-kommentarer beskriver riktiga parametrar, returvärden och undantag.
  4. Ta bort inaktuella TODO-kommentarer och bortkommenterad kod.
  5. Uppdatera tester och kommentarer samtidigt som beteendet ändras.

Skriv om kommentaren så att den beskriver orsaken till värdet och inte bara divisionen:

C# | Practice.cs
// Dela tiden med två.
int timeoutMilliseconds = serverLeaseMilliseconds / 2;
Visa ett lösningsförslag

Kommentaren ska förklara marginalen för nätverksfördröjningar. Använd bara förklaringen om den stämmer med det verkliga kravet.

C# | Practice.cs: lösningsförslag
// Förnya i god tid innan serverns lease går ut för att ge marginal för nätverksfördröjningar.
int timeoutMilliseconds = serverLeaseMilliseconds / 2;