AOSP Java-kodstil för bidragsgivare

Kodstilarna på den här sidan är strikta regler för att bidra med Java-kod till Android Open Source Project (AOSP). Bidrag till Android-plattformen som inte följer dessa regler accepteras i allmänhet inte. Vi är medvetna om att inte all befintlig kod följer dessa regler, men vi förväntar oss att all ny kod följer dem. Se Coding with Respect för exempel på terminologi som bör användas och undvikas för ett mer inkluderande ekosystem.

Var konsekvent

En av de enklaste reglerna är VAR KONSISTENT. Om du redigerar kod, ta några minuter för att titta på den omgivande koden och fastställa dess stil. Om den koden använder mellanslag runt if-klausulerna bör du också göra det. Om kodkommentarerna har små rutor med stjärnor runt omkring sig, se till att dina kommentarer också har små rutor med stjärnor runt omkring sig.

Punkten med stilriktlinjer är att ha en gemensam vokabulär för kodning, så att läsarna kan koncentrera sig på vad du säger, snarare än på hur du säger det. Vi presenterar globala stilregler här så att du känner till vokabulären, men lokal stil är också viktig. Om den kod som du lägger till i en fil ser drastiskt annorlunda ut än den befintliga koden runtomkring, får det läsarna att tappa rytmen när de läser den. Försök att undvika detta.

Java språkregler

Android följer vanliga Java-kodningskonventioner med de ytterligare regler som beskrivs nedan.

Ignorera inte undantag

Det kan vara frestande att skriva kod som ignorerar ett undantag, t.ex:

 void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { } }

Gör inte så här. Även om du kanske tror att din kod aldrig kommer att stöta på det här feltillståndet eller att det inte är viktigt att hantera det, skapar ignorerandet av den här typen av undantag minor i din kod som någon annan kan utlösa en dag. Du måste hantera varje undantag i din kod på ett principiellt sätt; den specifika hanteringen varierar beroende på fallet.

”Varje gång någon har en tom catch-klausul bör de få en läskig känsla. Det finns definitivt tillfällen då det faktiskt är rätt sak att göra, men man måste åtminstone tänka på det. I Java kan man inte undgå den läskiga känslan.” – James Gosling

Acceptabla alternativ (i prioriterad ordning) är:

  • Kasta undantaget upp till anroparen av din metod.
     void setServerPort(String value) throws NumberFormatException { serverPort = Integer.parseInt(value); }
  • Kasta ett nytt undantag som är lämpligt för din abstraktionsnivå.
     void setServerPort(String value) throws ConfigurationException { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new ConfigurationException("Port " + value + " is not valid."); } }
  • Hantera felet på ett elegant sätt och ersätt ett lämpligt värde i blocket catch {}
     /** Set port. If value is not a valid number, 80 is substituted. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { serverPort = 80; // default port for server } }
  • Fånga upp undantaget och kasta en ny instans av RuntimeException. Detta är farligt, så gör det bara om du är säker på att om detta fel inträffar är det lämpligt att krascha.
     /** Set port. If value is not a valid number, die. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new RuntimeException("port " + value " is invalid, ", e); } }
  • Som en sista utväg, om du är säker på att det är lämpligt att ignorera undantaget kan du ignorera det, men du måste också kommentera varför med en god anledning.
    /** If value is not a valid number, original port number is used. */void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { // Method is documented to just ignore invalid user input. // serverPort will just be unchanged. }}

Fånga inte upp generiska undantag

Det kan vara frestande att vara lat när man fångar upp undantag och göra något liknande:

 try { someComplicatedIOFunction(); // may throw IOException someComplicatedParsingFunction(); // may throw ParsingException someComplicatedSecurityFunction(); // may throw SecurityException // phew, made it all the way } catch (Exception e) { // I'll just catch all exceptions handleError(); // with one generic handler! }

Gör inte så här. I nästan alla fall är det olämpligt att fånga generiska Exception eller Throwable (helst inte Throwable eftersom det innehåller Error undantag). Det är farligt eftersom det innebär att undantag som du aldrig förväntat dig (inklusive körtidsundantag som ClassCastException) fångas upp i felhanteringen på appnivå. Det döljer felhanteringsegenskaperna i din kod, vilket innebär att om någon lägger till en ny typ av undantag i den kod som du anropar kommer kompilatorn inte att påpeka att du måste hantera felet på ett annat sätt. I de flesta fall bör du inte hantera olika typer av undantag på samma sätt.

De sällsynta undantagen från denna regel är testkod och kod på högsta nivå där du vill fånga upp alla typer av fel (för att förhindra att de visas i ett användargränssnitt eller för att hålla ett batchjobb igång). I dessa fall kan du fånga upp generiska Exception (eller Throwable) och hantera felet på lämpligt sätt. Tänk dock noga efter innan du gör detta, och lägg in kommentarer som förklarar varför det är säkert i det här sammanhanget.

Alternativ till att fånga upp generiska undantag:

  • Fånga upp varje undantag separat som en del av ett multi-catch-block, till exempel:
    try { ...} catch (ClassNotFoundException | NoSuchMethodException e) { ...}
  • Refaktorisera koden för att få en mer finfördelad felhantering, med flera try-block. Dela upp IO från parsning och hantera fel separat i varje fall.
  • Kasta bort undantaget. Många gånger behöver du inte fånga undantaget på den här nivån ändå, låt bara metoden kasta det.

Kom ihåg att undantag är din vän! När kompilatorn klagar på att du inte fångar ett undantag ska du inte rynka på näsan. Le! Kompilatorn har just gjort det lättare för dig att fånga upp körtidsproblem i din kod.

Använd inte finalizers

Finalizers är ett sätt att få en bit kod exekverad när ett objekt samlas in som skräp. Även om finalizers kan vara praktiska för rensning (särskilt av externa resurser) finns det inga garantier för när en finalizer kommer att anropas (eller ens att den kommer att anropas överhuvudtaget).

Android använder inte finalizers. I de flesta fall kan du istället använda bra undantagshantering. If you absolutely need a finalizer, define a close() method (or the like) and document exactly when that method needs to be called (see InputStream for an example). In this case, it’s appropriate but not required to print a short log message from the finalizer, as long as it’s not expected to flood the logs.

Fully qualify imports

When you want to use class Bar from package foo, there are two possible ways to import it:

  • import foo.*;

    Potentially reduces the number of import statements.

  • import foo.Bar;

    Makes it obvious what classes are used and the code is more readable for maintainers.

Use import foo.Bar; for importing all Android code. An explicit exception is made for Java standard libraries (java.util.*java.io.*, etc.) and unit test code (junit.framework.*).

Java-biblioteksregler

Det finns konventioner för användning av Androids Javabibliotek och verktyg. I vissa fall har konventionen ändrats på viktiga sätt och äldre kod kan använda ett föråldrat mönster eller bibliotek. När du arbetar med sådan kod är det okej att fortsätta med den befintliga stilen. När du skapar nya komponenter ska du dock aldrig använda föråldrade bibliotek.

Java stilregler

Varje fil ska ha en copyright-angivelse högst upp, följt av paket- och importangivelser (varje block åtskiljt av en tom rad) och slutligen klass- eller gränssnittsdeklarationen. I Javadoc-kommentarerna ska du beskriva vad klassen eller gränssnittet gör.

/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.android.internal.foo;import android.os.Blah;import android.view.Yada;import java.sql.ResultSet;import java.sql.SQLException;/** * Does X and Y and provides an abstraction for Z. */public class Foo { ...}

Varje klass och icke-trivial offentlig metod som du skriver måste innehålla en Javadoc-kommentar med minst en mening som beskriver vad klassen eller metoden gör. Denna mening ska börja med ett beskrivande verb i tredje person.

Exempel

/** Returns the correctly rounded positive square root of a double value. */static double sqrt(double a) { ...}

eller

/** * Constructs a new String by converting the specified array of * bytes using the platform's default character encoding. */public String(byte bytes) { ...}

Du behöver inte skriva Javadoc för triviala get- och set-metoder, till exempel setFoo(), om det enda som står i din Javadoc är ”sätter Foo”. Om metoden gör något mer komplext (t.ex. upprätthåller en begränsning eller har en viktig bieffekt) måste du dokumentera den. Om det inte är uppenbart vad egenskapen ”Foo” betyder bör du dokumentera det.

Alla metoder du skriver, offentliga eller andra, skulle ha nytta av Javadoc. Offentliga metoder är en del av ett API och kräver därför Javadoc. Android kräver ingen särskild stil för att skriva Javadoc-kommentarer, men du bör följa instruktionerna i Hur man skriver Doc-kommentarer för Javadoc-verktyget.

Skriv korta metoder

När det är möjligt bör du hålla metoderna små och fokuserade. Vi inser att långa metoder ibland är lämpliga, så det finns ingen hård gräns för metodernas längd. Om en metod överstiger 40 rader eller så, fundera på om den kan delas upp utan att skada programmets struktur.

Definiera fält på standardplatser

Definiera fält antingen högst upp i filen eller omedelbart före de metoder som använder dem.

Begränsad räckvidd för variabler

Håll räckvidden för lokala variabler till ett minimum. Detta ökar läsbarheten och underhållbarheten i din kod och minskar sannolikheten för fel. Deklarera varje variabel i det innersta blocket som omsluter all användning av variabeln.

Deklarera lokala variabler vid den punkt där de används för första gången. Nästan varje deklaration av lokala variabler bör innehålla en initialiserare. Om du ännu inte har tillräckligt med information för att initialisera en variabel på ett förnuftigt sätt, skjut upp deklarationen tills du har det.

Undantaget är try-catch-utsagor. Om en variabel initialiseras med returvärdet av en metod som kastar ett kontrollerat undantag måste den initialiseras inuti ett try-block. If the value must be used outside of the try block, then it must be declared before the try block, where it can’t yet be sensibly initialized:

// Instantiate class cl, which represents some sort of SetSet s = null;try { s = (Set) cl.newInstance();} catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible");} catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable");}// Exercise the sets.addAll(Arrays.asList(args));

However, you can even avoid this case by encapsulating the try-catch block in a method:

Set createSet(Class cl) { // Instantiate class cl, which represents some sort of Set try { return (Set) cl.newInstance(); } catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible"); } catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable"); }}...// Exercise the setSet s = createSet(cl);s.addAll(Arrays.asList(args));

Declare loop variables in the for statement itself unless there’s a compelling reason to do otherwise:

for (int i = 0; i < n; i++) { doSomething(i);}

and

for (Iterator i = c.iterator(); i.hasNext(); ) { doSomethingElse(i.next());}

Order import statements

The ordering of import statements is:

  1. Android imports
  2. Imports from third parties (comjunitnetorg)
  3. java and javax

To exactly match the IDE settings, the imports should be:

  • Alfabetiskt inom varje gruppering, med stora bokstäver före små bokstäver (till exempel Z före a)
  • Separerade med en tom rad mellan varje större gruppering (androidcomjunitnetorgjavajavax).

Ursprungligen fanns det inga stilkrav på ordningsföljden, vilket innebar att IDE:er antingen alltid ändrade ordningsföljden eller att IDE-utvecklare var tvungna att inaktivera de automatiska importhanteringsfunktionerna och manuellt underhålla importerna. Detta ansågs vara dåligt. När frågan om Java-stil ställdes varierade de föredragna stilarna kraftigt, och det kom fram till att Android helt enkelt måste ”välja en ordningsföljd och vara konsekvent”. Så vi valde en stil, uppdaterade stilguiden och fick IDE:erna att följa den. Vi förväntar oss att när IDE-användarna arbetar med koden kommer importen i alla paket att överensstämma med detta mönster utan extra teknisk ansträngning.

We chose this style such that:

  • The imports that people want to look at first tend to be at the top (android).
  • The imports that people want to look at least tend to be at the bottom (java).
  • Humans can easily follow the style.
  • IDEs can follow the style.

Put static imports above all the other imports ordered the same way as regular imports.

Use spaces for indentation

We use four (4) space indents for blocks and never tabs. When in doubt, be consistent with the surrounding code.

We use eight (8) space indents for line wraps, including function calls and assignments.

Recommended

Instrument i = someLongExpression(that, wouldNotFit, on, one, line);

Not recommended

Instrument i = someLongExpression(that, wouldNotFit, on, one, line);

Follow field naming conventions

  • Non-public, non-static field names start with m.
  • Static field names start with s.
  • Other fields start with a lower case letter.
  • Public static final fields (constants) are ALL_CAPS_WITH_UNDERSCORES.

For example:

public class MyClass { public static final int SOME_CONSTANT = 42; public int publicField; private static MyClass sSingleton; int mPackagePrivate; private int mPrivate; protected int mProtected;}

Use standard brace style

Put braces on the same line as the code before them, not on their own line:

class MyClass { int func() { if (something) { // ... } else if (somethingElse) { // ... } else { // ... } }}

We require braces around the statements for a conditional. Exception: If the entire conditional (the condition and the body) fit on one line, you may (but are not obligated to) put it all on one line. For example, this is acceptable:

if (condition) { body();}

and this is acceptable:

if (condition) body();

but this is not acceptable:

if (condition) body(); // bad!

Limit line length

Each line of text in your code should be at most 100 characters long. While much discussion has surrounded this rule, the decision remains that 100 characters is the maximum with the following exceptions:

  • Om en kommentarsrad innehåller ett exempelkommando eller en bokstavlig URL som är längre än 100 tecken, får den raden vara längre än 100 tecken för att underlätta klipp och klistra in.
  • Importrader kan överskrida gränsen eftersom människor sällan ser dem (detta förenklar också verktygsskrivandet).

Använd standard-Java-annotationer

Annotationer bör föregå andra modifieringar för samma språkelement. Enkla markeringsannotationer (till exempel @Override) kan listas på samma rad som språkelementet. Om det finns flera annotationer, eller parameteriserade annotationer, förtecknas de en per rad i alfabetisk ordning.

Androids standardpraxis för de tre fördefinierade annotationerna i Java är följande:

  • Använd @Deprecated-annotationen närhelst användningen av det annoterade elementet avråds. Om du använder @Deprecated-annotationen måste du också ha en @deprecated Javadoc-tagg och den bör namnge ett alternativt genomförande. Kom dessutom ihåg att en @Deprecated-metod fortfarande ska fungera. Om du ser gammal kod som har en @deprecated Javadoc-tagg, lägg till @Deprecated-annotationen.
  • Använd @Override-annotationen när en metod åsidosätter deklarationen eller implementationen från en överklass. Om du till exempel använder @inheritdocs Javadoc-taggen och härleder från en klass (inte ett gränssnitt) måste du också anteckna att metoden åsidosätter den överordnade klassens metod.
  • Använd @SuppressWarnings-annotationen endast under omständigheter där det är omöjligt att eliminera en varning. If a warning passes this ”impossible to eliminate” test, the @SuppressWarnings annotation must be used, to ensure that all warnings reflect actual problems in the code.

    When a @SuppressWarnings annotation is necessary, it must be prefixed with a TODO comment that explains the ”impossible to eliminate” condition. This normally identifies an offending class that has an awkward interface. Till exempel:

    // TODO: The third-party class com.third.useful.Utility.rotate() needs generics@SuppressWarnings("generic-cast")List<String> blix = Utility.rotate(blax);

    When a @SuppressWarnings annotation is required, refactor the code to isolate the software elements where the annotation applies.

Treat acronyms as words

Treat acronyms and abbreviations as words in naming variables, methods, and classes to make names more readable:

Good Bad
XmlHttpRequest XMLHTTPRequest
getCustomerId getCustomerID
class Html class HTML
String url String URL
long id long ID

As both the JDK and the Android code bases are inconsistent around acronyms, it’s virtually impossible to be consistent with the surrounding code. Therefore, always treat acronyms as words.

Use TODO comments for code that is temporary, a short-term solution, or good enough but not perfect. These comments should include the string TODO in all caps, followed by a colon:

// TODO: Remove this code after the UrlTable2 has been checked in.

och

// TODO: Change this to use a flag instead of a constant.

Om dinTODOhar formen ”Gör något vid ett framtida datum”, se till att du antingen anger ett specifikt datum (”Fixa senast i november 2005”) eller en specifik händelse (”Ta bort den här koden efter det att alla produktionsblandare har förstått protokoll V7.”).

Logga sparsamt

Även om loggning är nödvändig har den en negativ inverkan på prestandan och förlorar sin användbarhet om den inte hålls någorlunda kortfattad. Loggningsfaciliteterna tillhandahåller fem olika loggningsnivåer:

  • ERROR: Används när något ödesdigert har hänt, det vill säga något som kommer att få användarens synliga konsekvenser och som inte kan återställas utan att radera vissa data, avinstallera appar, radera datapartitioner eller återfylla hela enheten (eller värre). Denna nivå loggas alltid. Problem som motiverar viss loggning på ERROR-nivån är bra kandidater för att rapporteras till en server som samlar in statistik.
  • WARNING: Används när något allvarligt och oväntat har hänt, det vill säga något som kommer att få konsekvenser som är synliga för användaren men som sannolikt kan återställas utan dataförlust genom att utföra någon uttrycklig åtgärd, allt från att vänta eller starta om en app hela vägen till att ladda ner en ny version av en app eller starta om enheten. Denna nivå loggas alltid. Problem som motiverar loggning på nivån WARNING kan också övervägas för rapportering till en server för statistikinsamling.
  • INFORMATIVE: Används för att notera att något intressant har hänt, dvs. när en situation upptäcks som sannolikt kommer att få omfattande konsekvenser, men som inte nödvändigtvis är ett fel. Ett sådant tillstånd bör endast loggas av en modul som anser att den är den mest auktoritativa inom den domänen (för att undvika dubbel loggning av icke auktoritativa komponenter). Denna nivå loggas alltid.
  • DEBUG: Används för att ytterligare notera vad som händer på enheten som kan vara relevant för att undersöka och felsöka oväntade beteenden. Logga endast det som behövs för att samla in tillräckligt med information om vad som händer med din komponent. Om dina felsökningsloggar dominerar loggen bör du använda verbose-loggning.

    Den här nivån loggas även på release builds och måste omges av ett if (LOCAL_LOG) eller if LOCAL_LOGD) block, där LOCAL_LOG är definierat i din klass eller underkomponent, så att det finns en möjlighet att inaktivera all sådan loggning. Därför får det inte finnas någon aktiv logik i ett if (LOCAL_LOG) block. All stränguppbyggnad för loggningen måste också placeras i if (LOCAL_LOG)-blocket. Gör inte om loggningsanropet till ett metodanrop om det leder till att strängbyggnaden sker utanför if (LOCAL_LOG)-blocket.

    Det finns en del kod som fortfarande säger if (localLOGV). Detta anses också vara acceptabelt, även om namnet är icke-standardiserat.

  • VERBOSE: Används för allt annat. Denna nivå loggas endast vid felsökningsbyggen och bör omges av ett if (LOCAL_LOGV)-block (eller motsvarande) så att den kan kompileras ut som standard. Eventuell strängkompilering tas bort från release builds och måste visas inom blocket if (LOCAL_LOGV).

Anmärkningar

  • Inom en viss modul, förutom på VERBOSE-nivån, bör ett fel om möjligt bara rapporteras en gång. Inom en enda kedja av funktionsanrop inom en modul bör endast den innersta funktionen rapportera felet, och anropare i samma modul bör endast lägga till viss loggning om det påtagligt hjälper till att isolera problemet.
  • I en kedja av moduler, förutom på VERBOSE-nivå, när en modul på lägre nivå upptäcker ogiltiga data som kommer från en modul på högre nivå, bör modulen på lägre nivå endast logga denna situation till DEBUG-loggen, och endast om loggningen ger information som inte annars är tillgänglig för den som ringer. Det finns ingen anledning att logga situationer där ett undantag kastas (undantaget bör innehålla all relevant information), eller där den enda information som loggas finns i en felkod. Detta är särskilt viktigt i interaktionen mellan ramverket och appar, och förhållanden som orsakas av appar från tredje part som hanteras korrekt av ramverket bör inte utlösa loggning högre än DEBUG-nivån. De enda situationer som bör utlösa loggning på INFORMATIVE-nivån eller högre är när en modul eller app upptäcker ett fel på sin egen nivå eller som kommer från en lägre nivå.
  • När ett tillstånd som normalt skulle motivera viss loggning sannolikt kommer att inträffa många gånger kan det vara en bra idé att implementera någon mekanism för hastighetsbegränsning för att förhindra att loggarna svämmar över av många dubbletter av samma (eller mycket liknande) information.
  • Förluster av nätverksanslutning anses vara vanliga och är fullt förväntade, och bör inte loggas gratis. En förlust av nätverksanslutning som får konsekvenser inom en app bör loggas på DEBUG eller VERBOSE-nivå (beroende på om konsekvenserna är tillräckligt allvarliga och oväntade för att loggas i en release-build).
  • Att ha ett fullständigt filsystem på ett filsystem som är tillgängligt för eller på uppdrag av tredjepartsappar bör inte loggas på en högre nivå än INFORMATIV.
  • Ogiltiga data som kommer från någon icke betrodd källa (inklusive filer på delad lagring eller data som kommer via en nätverksanslutning) betraktas som förväntade och bör inte utlösa någon loggning på en högre nivå än DEBUG när det upptäcks att de är ogiltiga (och även då bör loggningen vara så begränsad som möjligt).
  • När den används på String-objekt skapar +-operatören implicit en StringBuilder-instans med standardbuffertstorlek (16 tecken) och potentiellt andra tillfälliga String-objekt. Att explicit skapa StringBuilder-objekt är alltså inte dyrare än att förlita sig på standardoperatören + (och kan vara mycket effektivare). Tänk på att kod som anropar Log.v() kompileras och exekveras vid utgivningsbyggen, inklusive att bygga strängarna, även om loggarna inte läses.
  • All loggning som är avsedd att läsas av andra personer och vara tillgänglig i release builds bör vara kortfattad utan att vara kryptisk, och bör vara begriplig. Detta inkluderar all loggning upp till DEBUG-nivån.
  • När det är möjligt bör loggningen hållas på en enda rad. Radlängder på upp till 80 eller 100 tecken är acceptabla. Undvik om möjligt längder som är längre än cirka 130 eller 160 tecken (inklusive taggens längd).
  • Om loggning rapporterar framgångar, använd den aldrig på högre nivåer än VERBOSE.
  • Om du använder tillfällig loggning för att diagnostisera ett problem som är svårt att reproducera, håll den på nivån DEBUG eller VERBOSE och omsluta den med if-block som gör det möjligt att inaktivera den vid kompilering.
  • Var försiktig med säkerhetsläckor via loggen. Undvik att logga privat information. Undvik särskilt att logga information om skyddat innehåll. Detta är särskilt viktigt när du skriver ramkod eftersom det inte är lätt att i förväg veta vad som kommer och inte kommer att vara privat information eller skyddat innehåll.
  • Använd aldrig System.out.println() (eller printf() för inhemsk kod). System.out och System.err omdirigeras till /dev/null, så dina utskriftsdeklarationer har inga synliga effekter. Men all stränguppbyggnad som sker för dessa anropningar utförs fortfarande.
  • Den gyllene regeln för loggning är att dina loggar inte i onödan får trycka ut andra loggar ur bufferten, precis som andra inte får trycka ut dina.

Javatests stilregler

Följ testmetodernas namngivningskonventioner och använd ett understreck för att skilja det som testas från det specifika fallet som testas. Denna stil gör det lättare att se vilka fall som testas. For example:

testMethod_specificCase1 testMethod_specificCase2void testIsDistinguishable_protanopia() { ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA) assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK)) assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))}

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *