Skip to content

Innlegg

Undersøkelse av argumentene for switch-setninger

6. desember 2015 • 9 min lesing

Undersøkelse av argumentene for switch-setninger

I nesten 50 år har switch-setningen (også kjent som case-setningen) vært en integrert del av programmering. De siste årene har imidlertid noen hevdet at switch-setningen har utlevert sitt formål. Andre går enda lenger ved å merke switch-setningen som en code-smell.

I 1952 konsiperte Stephen Kleene switch-setningen i sitt papir, Introduction to Metamathematics. Den første bemerkelsesverdige implementeringen var i ALGOL 58 i 1958. Senere ble switch-setningen inkludert i det uutslettelige C-programmeringsspråket, som, som vi vet, har påvirket de fleste moderne programmeringsspråk.

Spol frem til i dag og praktisk talt alle språk har en switch-setning. Imidlertid har noen få språk utelatt switch-setningen. Det mest bemerkelsesverdige er Smalltalk.

Dette vekket min nysgjerrighet, hvorfor ble switch-setningen ekskludert fra Smalltalk?

Andy Bower, en av skaperne/tilhengerne bak Dolphin Smalltalk delte sine tanker om hvorfor Smalltalk ekskluderte switch-setningen:

Da jeg først kom til Smalltalk fra C++, kunne jeg ikke forstå hvordan et såkalt fullt utviklet språk ikke støttet en switch/case-konstruksjon. Tross alt, da jeg først flyttet opp til “strukturert programmering” fra BASIC, trodde jeg at switch var en av de beste tingene siden skiveskåret brød. Imidlertid, fordi Smalltalk ikke støttet en switch, måtte jeg lete etter og forstå hvordan jeg kunne overvinne denne mangelen. Det riktige svaret er selvfølgelig å bruke polymorfisme og få objektene selv til å sende til riktig kodestykke. Da innså jeg at det ikke var en “mangel” i det hele tatt, men Smalltalk tvang meg til mye finere kornete OOP-design enn jeg hadde blitt vant til i C++. Hvis det hadde vært en switch-setning tilgjengelig, ville det ha tatt meg mye lengre tid å lære dette, eller verre, jeg kunne fortsatt programmere C++/Java pseudo-objekt-stil i Smalltalk.
Jeg vil hevde at i normal OOP er det ingen reell behov for en switch-setning. Noen ganger, når du grensesnitter til en ikke-OOP-verden (som å motta og sende WM_XXXX Windows-meldinger som ikke er objekter men bare heltall), ville en switch-setning være nyttig. I disse situasjonene finnes det alternativer (som sending fra en Dictionary) og antallet ganger de dukker opp rettferdiggjør ikke inkluderingen av tilleggssyntaks.

Hadde Andy rett? Er vi bedre stilt uten switch-setningen? Ville andre språk også ha nytte av å ekskludere switch-setningen?

For å kaste lys over dette spørsmålet, har jeg satt sammen en sammenligning mellom en switch-setning, en ordbok, og polymorfisme. La oss kalle det en smackdown. Måtte den beste implementeringen vinne!

Hver implementering har en metode som tar en parameter, et heltall, og returnerer en streng. Vi vil bruke syklomatisk kompleksitet og vedlikeholdsindeks for å undersøke hver implementering. Vi vil deretter ta et holistisk syn på alle tre implementeringene.

Koden.

Switch-setning

Vedlikeholdsindeks72
Syklomatisk kompleksitet6
    public class SwitchWithFourCases
    {
        public string SwitchStatment(int color)
        {
            var colorString = "Red";

            switch (color)
            {
                case 1:
                    colorString = "Green";
                    break;

                case 2:
                    colorString = "Blue";
                    break;

                case 3:
                    colorString = "Violet";
                    break;

                case 4:
                    colorString = "Orange";
                    break;

            }

            return colorString;
        }
    }

Ordbok

Vedlikeholdsindeks73
Syklomatisk kompleksitet3
public class DictionaryWithFourItems
{ 
    public string Dictionary(int color)
    {
        var colorString = "Red";
        var colors = new Dictionary<int, string> {{1, "Green"}, {2, "Blue"}, {3, "Violet"}, {4, "Orange"}};
        var containsKey = colors.ContainsKey(color);
        if (containsKey)
        {
            colorString = colors[color];
        }

        return colorString;
    }
}

Polymorfisme

Total vedlikeholdsindeks94
Total syklomatisk kompleksitet15

Grensesnitt

Vedlikeholdsindeks100
Syklomatisk kompleksitet1
public interface IColor
{
    string ColorName { get; }
}

Fabrikk

Vedlikeholdsindeks76
Syklomatisk kompleksitet4
public class ColorFactory
{
    public string GetColor(int color)
    {
        IColor defaultColor = new RedColor();
        var colors = GetColors();
        var containsKey = colors.ContainsKey(color);
        if (containsKey)
        {
            var c = colors[color];
            return c.ColorName;
        }

        return defaultColor.ColorName;
    }

    private static IDictionary<int, IColor> GetColors()
    {
        return new Dictionary<int, IColor>
        {
            {1, new GreenColor()}, 
            {2, new BlueColor()}, 
            {3, new VioletColor()}, 
            {4, new OrangeColor()}, 
            {5, new MagentaColor()}
        };
    }
}

Implementering

Vedlikeholdsindeks97
Syklomatisk kompleksitet2
public class BlueColor : IColor
{
    public string ColorName => "Blue";
}

public class RedColor : IColor
{
    public string ColorName => "Red";
}

public class GreenColor : IColor
{
    public string ColorName => "Green";
}

public class MagentaColor : IColor
{
    public string ColorName => "Magenta";
}

public class VioletColor : IColor
{
    public string ColorName => "Violet";
}

Resultatene

Før jeg dykker ned i resultatene, la oss definere syklomatisk kompleksitet og vedlikeholdsindeks:

  • Syklomatisk kompleksitet er målet på logisk forgrening. Jo lavere tall, jo bedre.
  • Vedlikeholdsindeks måler vedlikeholdsbarheten av koden. Det er på en skala mellom 0 og 100. Jo høyere tall, jo bedre.
Syklomatisk kompleksitetVedlikeholdsindeks
Switch-setning672
Ordbok373
Polymorfisme1594

Vi vil først undersøke syklomatisk kompleksitet.

Resultatene for syklomatisk kompleksitet er enkle. Ordbokimplementeringen er den enkleste. Betyr dette at det er den beste løsningen? Nei, som vi vil se når vi evaluerer vedlikeholdsindeksen.

De fleste ville tenke som jeg gjorde, implementeringen med lavest syklomatisk kompleksitet er den mest vedlikeholdbar — hvordan kunne det være annerledes?

I vårt scenario er implementeringen med lavest syklomatisk kompleksitet ikke den mest vedlikeholdbar. Faktisk i vårt scenario er det det motsatte. Den mest komplekse implementeringen er den mest vedlikeholdbar! Sinnet spratt!

Hvis du husker, jo høyere vedlikeholdsindeks-poengsum, jo bedre. For å komme til poenget, polymorfisme har den beste vedlikeholdsindeks-poengsum — men den har også høyest syklomatisk kompleksitet. Hva gir? Det virker ikke riktig.

Hvorfor er den mest komplekse implementeringen den mest vedlikeholdbar? For å svare på dette, må vi forstå vedlikeholdsindeksen.

Vedlikeholdsindeksen består av 4 metrikker: syklomatisk kompleksitet, antall linjer med kode, antall kommentarer og Halstead-volum. De tre første metrikene er relativt velkjente, men den siste, Halstead-volumet, er relativt ukjent. Som syklomatisk kompleksitet, forsøker Halstead-volumet objektivt å måle kodekompleksitet.

I enkle termer måler Halstead-volum antall bevegelige deler (variabler, systemkall, aritmetikk, kodekonstruksjoner, osv.) i kode. Jo høyere antall bevegelige deler, jo mer kompleksitet. Jo lavere antall bevegelige deler, jo lavere kompleksitet. Dette forklarer hvorfor den polymorfiske implementeringen scorer høyt på vedlikeholdsindeksen; klassene har lite eller ingen bevegelige deler. En annen måte å se på Halstead-volumet er at det måler “bevegelige deler”-tetthet.

Hva er programvare, hvis det ikke er for å endre? For å reflektere den virkelige verden, introduserer vi endring. Jeg har lagt til en ny farge til hver implementering.

Nedenfor er de reviderte resultatene.

Syklomatisk kompleksitetVedlikeholdsindeks
Switch-setning770
Ordbok373
Polymorfisme1795

Switch-setningen og de polymorfiske tilnærmingene økte begge i syklomatisk kompleksitet med en enhet, men interessant nok økte ikke ordboken. Først var jeg forvirret over dette, men så innså jeg at ordboken betrakter fargene som data og de to andre implementeringene behandler fargene som kode.Jeg skal komme til kjernen av saken.

Med oppmerksomheten på vedlikeholdsindeksen, bare en, switch-setningen, reduserte vedlikeholdsbarheten. Polymorfismens vedlikeholdspoengsum forbedret seg og likevel økte kompleksiteten også (vi ville foretrekke at den skulle avta). Som jeg nevnte ovenfor, er dette kontraintuitivt.

Vår sammenligning viser at ordbøker kan, fra et kompleksitetsstandpunkt, skaleres uendelig. Den polymorfiske tilnærmingen er langt den mest vedlikeholdbar og ser ut til å øke i vedlikeholdbarhet når flere scenarier legges til. Switch-setningen øker i kompleksitet og reduseres i vedlikeholdbarhet når det nye scenarioet ble lagt til. Selv før vi la til det nye scenarioet, hadde det den verste syklomatiske kompleksiteten og vedlikeholdsindeksmål.

Jem Finch fra Google delte sine tanker om switch-setningens mangler:

1. Polymorfiske metodimplementeringer er leksikalsk isolert fra hverandre. Variabler kan legges til, fjernes, endres, og så videre uten noen risiko for å påvirke urelatert kode i en annen gren av switch-setningen.

2. Polymorfiske metodimplementeringer er garantert å returnere til riktig sted, forutsatt at de avsluttes. Switch-setninger i et fall-through-språk som C/C++/Java krever en feilutsatt “break”-setning for å sikre at de returnerer til setningen etter switch i stedet for neste case-blokk.

3. Eksistensen av en polymorfisk metodimplementering kan håndheves av kompilatoren, som vil nekte å kompilere programmet hvis en polymorfisk metodimplementering mangler. Switch-setninger gir ingen slik uttømmende kontroll.

4. Polymorfisk metodsending er utvidbar uten tilgang til (eller omkompilering av) annen kildekode. Å legge til et annet tilfelle til en switch-setning krever tilgang til den opprinnelige sendingskoden, ikke bare på ett sted men på alle steder der den relevante enum blir byttet.

5. … du kan teste polymorfiske metoder uavhengig av bytteapparatet. De fleste funksjoner som bytter som eksemplet forfatteren ga vil inneholde annen kode som ikke kan testes separat; virtuelle metodekall kan derimot.

6. Polymorfiske metodekall garanterer konstant tidsending. Ingen tilstrekkelig smart kompilator er nødvendig for å konvertere det som naturlig er en lineær tidskonstruksjon (switch-setningen med fall-through) til en konstant tidskonstruksjon.

Dessverre, eller heldigvis, avhengig av din leir, har de fleste språk en switch-setning, og de kommer ikke noe sted snart. Med dette i tankene, er det bra å vite hva som skjer under panseret når du kompilerer switch-setninger.

Det er tre switch-setningsoptimaliseringer som kan oppstå:

  1. If-elseif-setninger – Når en switch-setning har et lite antall tilfeller eller sparsomme tilfeller (ikke-inkrementelle verdier, som 10, 250, 1000) konverteres den til en if-elseif-setning.
  2. Hoppbord – I større sett med tilstøtende tilfeller (1, 2, 3, 4, 5) konverterer kompilatoren switch-setningen til et hoppbord. Et hoppbord er i hovedsak en hashtabell med en peker (tenk goto-setning) til funksjonen i minnet.
  3. Binært søk – For store sett med sparsomme tilfeller kan kompilatoren implementere et binært søk for å identifisere saken raskt, på samme måte som en indeks fungerer i en database. I ekstraordinære tilfeller der tilfeller er et stort antall sparsomme og tilstøtende tilfeller, vil kompilatoren bruke en kombinasjon av de tre optimaliseringene.

Oppsummering

I en objektorientert verden er switch-setningen, konsipiert i 1952, en stabil del av programvareingeniøren. Et bemerkelsesverdigt unntak er Smalltalk der designerne valgte å ekskludere switch-setningen.

Når den sammenlignes med alternative ekvivalente implementeringer, ordboken og polymorfismen, gjorde switch-setningen det ikke like bra.

Switch-setningen er her for å bli, men som vår sammenligning har vist, finnes det bedre alternativer til switch-setningen.

Implementeringene er tilgjengelige på Github.

Forfatter: Chuck Conway er en AI-ingeniør med nesten 30 års erfaring innen programvareutvikling. Han bygger praktiske AI-systemer—innholdspipelines, infrastrukturagenter og verktøy som løser virkelige problemer—og deler det han lærer underveis. Koble til ham på sosiale medier: X (@chuckconway) eller besøk ham på YouTube og på SubStack.

↑ Tilbake til toppen

Du kan også like