66
Arga programmeraren SAS User Forum 2017

Arga programmeraren - Home - SAS Support …Arga programmeraren" som feedbackverktyg Hoppa över review för att slippa det jobbiga feedback-steget • Gemensamt ansvar för att det

  • Upload
    vodieu

  • View
    215

  • Download
    0

Embed Size (px)

Citation preview

Arga programmerarenSAS User Forum 2017

Fredrik Hansson

• Konsult, SAS Institute, 2001-2007

• SAS-utvecklare, UCR, 2007-2017

Fokus på SAS/BASE och SAS/MACRO

En bättre produkt med lägre förvaltningskostnader

Programkod Metoder Verktyg

•Programkod•Verktyg•Metoder

Exempel på hur man kan utveckla program som är lätta att• Förvalta

• Felsöka

• Vidareutveckla

UCR - kärnverksamhet

Kvalitetsregister

Kliniska prövningar

Biobank och labb

Kvalitetsregister mäter vårdkvalitet

• Insamling av vårddata (inmatning eller integration med journalsystem)

• Analys/rapportering av insamlat data (görs till största delen i SAS)

• Vilket sjukhus ger bäst vård? Vad kan vi kopiera till andra sjukhus?

• Är något fel hos de sjukhus har sämst resultat?

• Vilka behandlingar är bäst?

• Får kvinnor och män lika bra behandling?

Uppgift

Fotregistret

Base(stage)

Mart Rapport(er)

Nattligtbatchjobb

Online-rapporter

Fotregistret

Bra vårdkvalitet

• Ordinerar ultraljudsbehandling till patienter med diagnos ”hälsporre”.

• Ordinerar mjukgörande kräm till patienter med diagnos ”torra fötter”.

Om en vårdgivare inte uppfyller dessa krav i tillräckligt hög utsträckning behöver man göra en genomgång av vårdgivarens vårdprocess.

Kvalitetsregister bidrar till

• Att Sverige har bland de bästa överlevnadsfrekvenserna efterhjärtattack, stroke, bröst- och ändtarmscancer.

• Att Sverige är ledande inom områden som akut hjärtsjukvård, diabetesvård och höftledsoperationer.

• Utveckling av innovativa e-hälsotjänster, patient-centreradetillvägagångssätt och beslutssödsfunktionalitet.

Efter konsultens arbete

• Leverans enligt tidplan• Leverans enligt budget

Efter konsultens arbete

Glad UCR-chef

Efter konsultens arbete

Glad kund

Efter konsultens arbete

Arg programmerare (förvaltare)

Kodexempel

Kundkrav

1. Patienter som har ett torrhetsindex > 40 eller har självsprickor, ska kategoriseras som patienter med torra fötter.

proc format library=format.formats;

value tf

1="Torra fötter"

2="Ej torra fötter";

quit;

data work.besok;

set base.besok;

attrib tf

length=3

format=tf.;

* Torrhetsindex över 40 eller självsprickor innebär

* att patienten lider av torra fötter ;

if torrhetsindex > 40 or Sjalvsprickor="JA" then tf = 2;

else tf = 1;

run;

Vad i hela friden är tf?

proc format library=format.formats;

value torra_fotter

1="Torra fötter"

2="Ej torra fötter";

quit;

data work.besok;

set base.besok;

attrib torra_fotter

length=3

format=torra_fotter.;

if torrhetsindex > 40 or Sjalvsprickor="JA" then torra_fotter = 2;

else torra_fotter = 1;

run;

tf -> torra_fotter

Exempel på andra dåliga variabelnamn

• temp

• x

• y

• [Programmerarens namn]

Exempel från verkligheten!

Annat viktigt att namnsätta bra

• Macroprogram och macrovariabler

• Dataset

• Format

• Libnames (endast 8 tecken)

• Antagligen mycket mer…

trettiotva_tecken_ar_mycket---------1----1----2----2----3------5----0----5----0----5----0--

Tillbaka till programmet

proc format library=format.formats;

value torra_fotter

1="Torra fötter"

2="Ej torra fötter";

quit;

data work.besok;

set base.besok;

attrib torra_fotter

length=3

format=torra_fotter.;

if torrhetsindex > 40 or Sjalvsprickor="JA" then torra_fotter = 2;

else torra_fotter = 1;

run;

tf -> torra_fotterÄr det någon som har sett buggen?

proc format library=format.formats;

value $torra_fotter

"JA"="Torra fötter"

"NEJ"="Ej torra fötter";

quit;

data work.besok;

set base.besok;

attrib torra_fotter

length=$16

format=$torra_fotter.;

if torrhetsindex > 40 or Sjalvsprickor="JA" then torra_fotter = "NEJ";

else torra_fotter = "JA";

run;

Sifferkoder utbytta mot läsbara textkoder.

Buggen blir uppenbar.

Sifferkoder eller textkoder

• Sifferkoder kan användas för att få data presenterat i en viss ordning• Man kan lägga till en sorteringssiffra i text-koder (”01-JA”)

• Sifferkoder tar mindre utrymme på disk• Ja. Men är det verkligen så att du alltid måste vara restriktiv med utrymme?

Är det inte bara gammal vana? Kan inte läsbar kod vara mer värd än diskutrymme? Arbetstid kostar ju också. För att inte tala om buggar…

proc format library=format.format;

value $torra_fotter

"JA"="Torra fötter"

"NEJ"="Ej torra fötter";

quit;

data work.besok_torra_fotter;

set base.besok;

attrib torra_fotter

length=$16

format=$torra_fotter.;

if torrhetsindex > 40 or Sjalvsprickor="JA" then torra_fotter = "JA";

else torra_fotter = "NEJ";

run;

Buggen är fixad.

Beskrivande variabelnamn och

användning av läsbara koder minskar behovet

av kommentarer i koden…

.. och minskar behovet av extern

dokumentation.

Sammanfattning variabelnamn och textkoder

KundkravBra vårdkvalitet

• Ordinerar ultraljudsbehandling av hälsporre mer än 95% av fallen.

• Ordinerar mjukgörande kräm till torra fötter i mer än 75% av fallen.

Kvalitetsindex-variabler

1=Uppfyller kvalitetsmålet

0=Uppfyller inte kvalitetsmålet

.=Ska inte vara med i urvalet

(Vi ska senare beräkna ett medelvärde. Det berättigar användningen av 1,0 istället för Ja,Nej.)

data work.kvalitet_per_besok;

set work.besok_torra_fotter;

attrib

QI_mjukgorande_kram_ordinerad length=3

QI_ultraljudsbeh_ordinerad length=3;

if torra_fotter = "JA" then do;

if mjukgorandeKramUtskriven="JA" then QI_mjukgorande_kram_ordinerad=1;

else QI_mjukgorande_kram_ordinerad=0;

end;

if Halsporre = "JA" then do;

if ultraljudsbehandling_ordinerad="JA" then QI_ultraljudsbeh_ordinerad=1;

else QI_ultraljudsbeh_ordinerad=0;

end;

run;

Vad händer om mjukgorandeKramUtskriven=""?

Hur vet förvaltaren om utvecklaren har tänkt rätt, eller

bara har missat att det skulle kunna hända?

<KLIPP>

if torra_fotter = "JA" then do;

if mjukgorandeKramUtskriven="JA" then do;

QI_mjukgorande_kram_ordinerad=1;

end;

else if mjukgorandeKramUtskriven in("NEJ", "") then do;

QI_mjukgorande_kram_ordinerad=0;

end;

else do;

ERROR "Programmet xxxxxx.sas har tagit emot ett oväntat värde (”

mjukgorandeKramUtskriven") i kolumnen mjukgorandeKramUtskriven i tabellen

work.besok_torra_fotter";

abort abend;

end;

end;

else do;

QI_mjukgorande_kram_ordinerad=.;

end;

<KLIPP>

Nu är det tydligt vad som händer om

mjukgorandeKramUtskriven=""

Felhantering för oväntade/felaktiga

värden

Att tänka på i if-satser och select-when

• Gör det tydligt vad som ska hända för alla väntade värden

• Se till att programmet inte släpper igenom oväntade/felaktiga värden.• Det är oftast värre att ett fel passerar obemärkt, än att nattbatchen kraschar!

Kvalitetsindikatorerna är de viktigaste affärsreglerna i registret.Vi behöver verifiera att beräkningarna är korrekta!

Alternativ 1 (”Bror Duktig”)• Skapa upp ett test-in-dataset och ett test-ut-dataset.

• Beräkna manuellt för varje rad i test-in-datasetet vad kvalitets-indikatorerna ska få för värden. Spara i test-ut-datasetet.

• Kör koden mot test-indata-setet. Jämför resultatet med testutdatasetet (t.ex. med proc compare).

• Håll testdataseten uppdaterade i framtiden, så att inte tillägg, av nya variabler förstör testfallet.

Alternativ 2 (”Slarvern”)• Ta en kaffepaus• Lägg tio minuter på att testköra med produktionsdata.• Göra några stickprov för att kontrollera att det verkar stämma.

Alternativ 3• Bryt ut de viktiga affärsreglerna till funktioner som:

• gör datasteget ovan mer läsbart.• gör affärsregeln testbar separat från övrig programlogik (enhetstestning).• gör affärsregeln återanvändbar.

• Skriv testfall för funktionerna• Ta en kaffepaus

Kvalitetsindikatorerna är de viktigaste affärsreglerna i registret.Vi behöver verifiera att beräkningarna är korrekta!

proc FCMP

Proc för att skapa egna funktioner

Säljs in som ett sätt att skapa egna generella funktioner.

Det största användningsområdet är att göra egna funktioner med väldigt specifika syften. OBS! Åsikt.

Åter till programmet

• Efter att vi kompleterade if-satserna med else-if och else, blev datasteget långt, och ganska svårläst.

<KLIPP>

if torra_fotter = "JA" then do;

if mjukgorandeKramUtskriven="JA" then do;

QI_mjukgorande_kram_ordinerad=1;

end;

else if mjukgorandeKramUtskriven in("NEJ", "") then do;

QI_mjukgorande_kram_ordinerad=0;

end;

else do;

ERROR "Programmet xxxxxx.sas har tagit emot ett oväntat värde (”

mjukgorandeKramUtskriven") i kolumnen mjukgorandeKramUtskriven i tabellen

work.besok_torra_fotter";

abort abend;

end;

end;

else do;

QI_mjukgorande_kram_ordinerad=.;

end;

<KLIPP>

Och lika mycket kod till för ultraljudsbehandling av

hälsporre…

data work.kvalitet_per_besok;

set work.besok_torra_fotter;

attrib

QI_mjukgorande_kram_ordinerad length=3

QI_ultraljudsbeh_ordinerad length=3;

QI_mjukgorande_kram_ordinerad=

ratt_ordination_torra_fotter(torra_fotter, mjukgorandeKramUtskriven);

QI_ultraljudsbeh_ordinerad=

ratt_ordination_halsporre(Halsporre, ultraljudsbehandling_ordinerad);

run;

De komplicerade if-satserna har brutits ut till funktioner

Exempel på funktion i proc FCMPproc fcmp outlib=work.functions.testdata;

function ratt_ordination_torra_fotter(torra_fotter $, mjukgorandeKramUtskriven $);

if torra_fotter = "JA" then do;

if mjukgorandeKramUtskriven="JA" then do;

return(1);

end;

else if mjukgorandeKramUtskriven in("NEJ", "") then do;

return(0);

end;

else do;

put "ERROR: Funktionen ratt_ordination_torra_fotter har tagit emot ett oväntat

värde (" mjukgorandeKramUtskriven") i parametern mjukgorandeKramUtskriven";

abort;

return(.);

end;

end;

else do;

return(.);

end;

endsub;

run;

Men…

• …om man bryter ut logiken, så ser man den ju inte i programmet.• Sant. Men så fort logiken blir lite komplicerad, så gör det datasteget svårt att

överblicka. Och då är vinsten att bryta ut logiken större.

• …det där kan jag göra med ett macro.• Sant. Men det kräver mycket disciplin att skapa macron som är tydligt

avgränsade i funktionalitet. Styrkan med funktioner, är att man inte kan göra allt som man kan göra i macron.

Och hur var det med testning då?

Skapa en test-funktionfunction test_numeric_funktions(testanrop, forvantat_svar, testId $, beskrivning $) $;

if testanrop = forvantat_svar then do;

put "Testet " testid " passerade utan problem";

return("OK");

end;

else do;

put " ";

put "Testet " testid " fallerade.";

put "ERROR: Testet fallerade.";

put "Funktionen returnerade:" testanrop ". Förväntat svar var: " forvantat_svar ;

put beskrivning;

put " ";

return("FAIL");

end;

endsub;

Kör testfallen i ett data _null_-steg

data _null_;

svar=test_numeric_funktions(ratt_ordination_halsporre("JA", "JA"),

1 /* Förväntat svar */,

"TEST_0001" /* Test-id */,

"Testet kontrollerar att patienter med hälsporre, som

ordinerats ultraljudsbehandling får en etta (uppfyller

kvalitetskraven)");

svar=test_numeric_funktions(ratt_ordination_halsporre("JA", "NEJ"),

0 /* Förväntat svar */,

"TEST_0002" /* Test-id */,

"Testet kontrollerar att patienter med hälsporre, som

inte ordinerats ultraljudsbehandling får en nolla

(uppfyller ej kvalitetskraven)");

run;

Och sen en snabb titt i loggen

Testet TEST_0001 passerade utan problem

Testet TEST_0002 passerade utan problem

ERROR: Testet TEST_0003 fallerade.

Funktionen returnerade: 0 Förväntat svar var: .

Provocerar fram ett misslyckat test

NOTE: DATA statement used (Total process time):

real time 0.14 seconds

cpu time 0.06 seconds

Sammanfattning funktioner

• Gör programkod mer läsbart.

• Separerar affärsregeln från övrig programlogik. Underlättar test!

• Gör affärsregeln återanvändbar.

Rapportprogrammet

%printemptymsg(urval_data);

%if &nobs gt 0 %then %do;

…men hur ska man kunna veta det?

&nobs skapas som englobal macrovariabel i“printemptymessage”

Och vad gör macrotegentligen?

Namnsättning…

Det ska vara tydligt vad som returneras från ett macro

%let nobs=%printemptymsg(urval_data);

%if &nobs gt 0 %then %do;

%printemptymsg(urval_data, out_number_of_observations=nobs);

%if &nobs gt 0 %then %do;

%count_observations(urval_data,

out_number_of_observations= number_of_observations);

%if &number_of_observations gt 0 %then %do;

%let number_of_observations=%count_observations(urval_data);

%if &number_of_observations gt 0 %then %do;

Och det där med namnsättning…

%if &nobs gt 0 %then %do;

%end;

%macro abort_report(abort_condition=,

Message=);

%if not (&abort_condition) %then %do;

%return;

%end;

%else %do;

%stpbegin;

proc odstext;

p "&Message";

run;

%stpend;

endsas;

%end;

%mend abort_report;

%abort_report(abort_condition= &nobs EQ 0,Message=Rapport kunde inte skapas pga…)

Pekpinnar om macron

• Det ska vara tydligt vad som returneras.• %let returvariabel=%mitt_macro(eventuella_inparametrar=);

• %mitt_macro(eventuella_inparametrar=, returvariabel1=, returvariabel2=);

• Packa inte in kod i macron, om det inte är nödvändigt.

• Låt ett macro ha en uppgift. Om det är svårt att namnsätta macrot, beror det antagligen på att det gör för många saker.

Summering programkod

• Bla

• Bla

• Bla

• proc FCMP

• Bla

• Bla

• Bla

•Programkod•Verktyg•Metoder

Snabbaste sättet att minska antal producerade buggar

Skriv inte koden över huvud taget

Mycket egenskriven kod går att ersätta med verktyg• DI Studio

• Enterprise Guide

• VA

• SAS Studio

•Programkod•Verktyg•Metoder

”Mjuka” delar i programmering

• Människor

• Processer

Utveckling

Review av annan programmerare

Testavdelning

"Arga programmeraren" som feedbackverktyg

Hoppa över review för att slippa det jobbiga feedback-steget

• Gemensamt ansvar för att det blir ett bra program• Färre dumheter (som inte påverkar programmets funktion

men bara gör det svårläst), bollas sällan tillbaka vid vanlig review, men stoppas vid parprogrammering.

• Kunskapsutbyte integreras i det dagliga arbetet.

Programmera tillsammans istället

Har vi råd att parprogrammera?

Att ha två programmerare dubblar ju kostnaden för att utveckla en programvara!

En bugg är alltid dyrare ju senare den hittas

Bilden från http://blog.celerity.com/the-true-cost-of-a-software-bug

Siffror från NIST-studien

Men vi skriver ju inte så många buggar

Industristandard: "about 15 - 50 errors per 1000 lines of delivered code."Microsoftapplikationer: "about 10 - 20 defects per 1000 lines of code during in-house testing, and 0.5 defect per KLOC (KLOC IS CALLED AS 1000 lines of code) in released product.”

"Code Complete" by Steve McConnellhttp://amartester.blogspot.se/2007/04/bugs-per-lines-of-code.html

80% av kostnaden för en mjukvara kommer efter att man har skrivit den första gången."In the modern software environment, 80% of the cost of the software is spent after the software is written the first time..."

John Munson, professor of computer science,the University of Idahohttps://www.fastcompany.com/28121/they-write-right-stuff

Har vi råd att inte parprogrammera?

En bättre produkt med lägre förvaltningskostnader

Skriv läsbar och testbar kodJobba mer/bättre

tillsammansAnvänd verktyg när det går

Nästan slut nu….

Övriga små tips

• Googla ”SAS coding conventions” (men hoppa över sådant som inte spelar roll för kodens läsbarhet)

• Använd ett versionshanteringssystem (t.ex. GIT, SVN, CVS)

Det var allt

Diskutera mer?

[email protected]

• Beskrivande namnsättning.

• Använd läsbara koder (ej siffror).

• Skriv kod som explicit beskriver vad som ska hända för varje väntat fall.

• Skriv kod som hanterar oväntade fall.

• Använd funktioner för att kapsla in viktig logik.

• Det ska vara tydligt vilka eventuella macrovariabler som returneras från ett macro.

• Undvik att lägga kod i ett macro så långt det är möjligt.

• Skriv macron som gör en sak. Dela annars upp i fler macron.

• Använd ett versionshanteringssystem

• Använd verktyg (DI Studio, VA, m.fl.) när det går.

• Ge parprogrammering en ordentlig chans.