2009-10-15 14 views
5

Quindi ho avuto una semplice funzione ucwords per Perl che ho avuto un po 'di tempo, e volevo espanderla, questo è quello che mi è venuto in mente, è questo il modo in cui dovrei costruire le mie funzioni per gestire opzionali parametri?È questa la strada da percorrere per creare subroutine Perl?

originale:

sub ucwords{ 
    $str = @_[0]; 
    $str = lc($str); 
    $str =~ s/\b(\w)/\u$1/g; 
    return $str; 
} 

estesa:

sub ucwords{ 
    if(@_[0] ne undef){#make sure some argument was passed 
     @overloads = (0,1,2,3); 
     $str = @_[0]; 
     if(@_[1] eq undef || @_[1] eq 0){ #default is to lowercase all but first 
      $str = lc($str); 
      $str =~ s/\b(\w)/\u$1/g; 
      return $str; 
     }else{ #second parameters 
      if(!grep $_ eq @_[1], @overloads){ die("No overload method of ucwords() takes "[email protected]_[1]." as second parameter."); } 
      if(@_[1] eq 1){ $str =~ s/\b(\w)/\u$1/g;} #first letter to upper, remaining case maintained 
      if(@_[1] eq 2){ $str = lc($str); $str =~ s/(\w)\b/\u$1/g;} #last letter to upper, remaining to lower 
      if(@_[1] eq 3){ $str =~ s/(\w)\b/\u$1/g;} #last letter to upper, remaining case maintained 
      return $str; 
     } 
    }else{ 
     die("No overload method of ucwords() takes no arguments"); 
    } 
} 

Psy

+5

tl; dr, ma il tuo codice non lo fa? 'join '' map {ucfirst} split/(\ s +)/$ stringa'? – Ether

+3

@Psytronic dovresti leggere 'perldoc perlsub': http://perldoc.perl.org/perlsub.html –

+0

Grazie per il consiglio, non ero particolarmente attento ai commenti riguardo la funzione stessa, so che probabilmente ci sono migliori modi di fare il lavoro, è stato più commenti riguardo al modo in cui l'ho costruito, perché non voglio fare cose sbagliate dall'inizio e ripeterlo più e più volte. – Psytronic

risposta

25

In una parola: NO! sguardo

Let a:

sub ucwords{ 
    $str = @_[0]; 
    $str = lc($str); 
    $str =~ s/\b(\w)/\u$1/g; 
    return $str; 
} 

In primo luogo, non si utilizza strict. Usalo. È per il tuo bene.

In secondo luogo, non si utilizza warnings. Usalo. È per il tuo bene. Ad esempio, il primo elemento di @_ deve essere riferito a $_[0] e non@_[0].

In terzo luogo, si dovrebbe prendere l'abitudine di leggere la lista FAQ volte prima di ri-inventare la ruota di nuovo: Vedi How do I capitalize all the words on one line?

Se pensi che questo è dura, si consideri il fatto che, quando chiamato come:

print ucwords("FRED AND BARNEY'S LODGE"), "\n"; 

vostre uscite codice

 
Fred And Barney'S Lodge 

che è l'esempio dato in questa domanda.

Inoltre, avendo una funzione che fa più di una cosa, sceglie quello che fa sulla base di numeri misteriosi e nessuna di quelle cose giuste non è una buona strategia di progettazione.

Dovresti invece avere più funzioni, denominate in un modo che può essere compreso da un lettore casuale del tuo codice, ognuna delle quali fa solo una cosa e fa ciò.

Infine, la versione estesa della funzione (senza dire nulla circa la saggezza di scrivere una tale funzione) può essere meglio scritto come:

# untested code follows 

use Carp; 

{ 
    my %modes = map {$_ => undef} 0 .. 3; 
    sub ucwords{ 
     croak 'No arguments passed' unless @_; 

     my ($str, $mode) = @_; 
     $mode = 0 unless defined $mode; 

     croak "Invalid mode: '$mode'" unless exists $modes{$mode}; 

     if ($mode == 0) { 
      $str = lc($str); 
      $str =~ s/\b(\w)/\u$1/g; 
     } 
     elsif ($mode == 1) { 
      $str =~ s/\b(\w)/\u$1/g;   
     } 
     elsif ($mode == 2) { 
      $str = lc($str); 
      $str =~ s/(\w)\b/\u$1/g;   
     } 
     else { 
      $str =~ s/(\w)\b/\u$1/g; 
     } 

     return $str; 
    } 
} 

Vedi anche Why use if-else if in C++?

+4

Penso che dovresti aver risposto due volte in questo caso. Una volta con la tua risposta originale e una volta con la versione corrente. Avresti guadagnato due voti da me in quel caso. – innaM

+0

Sono d'accordo che dovrebbe essere un'altra risposta, ma non so se avrò svalutato il primo, come solo dicendo di usare rigorosi e gli avvertimenti in realtà non fanno notare gli altri problemi con la funzione. Ad ogni modo, +1 per questo. :) – NateDSaint

+1

Dovrebbe essere ovvio ... ma come la persona che gestisce gli script nel mio lavoro, lo dirò ... Se dovessi usare qualcosa come la funzione estesa sopra - DOCUMENTa. Dai un nome alle tue modalità, se solo nei commenti. – Rini

4

può essere che si trova Params::Validate utile. Può essere usato per convalidare i parametri secondo varie regole. Ecco come potrebbe apparire nel tuo caso:

## somewhere is the start of the module 
use Params::Validate qw(:all); 

sub ucwords { 
    ## this line helps to undestand which parameter should be passed to function 
    my ($string, $algorithm_id) = @_; 

    ## make sure that 2 scalar parameters passed 
    validate_pos(@_, {'type' => SCALAR}, {'type' => SCALAR}); 

    ## main code here 
} 
11

Non utilizzare il $foo ne undef costrutto. Gli operatori in Perl sono conosciuti come "sensibili al contesto". Utilizzando determinati operatori, si introducono determinati contesti.ne, eq, lt, gt, le, ge sono tutti operatori "stringa", trattando le scalari su entrambi i lati come stringhe, che ==, !=, <, >, <=, >= sono operatori numerici, trattando l'oggetto su entrambi i lati come un numero.

Tuttavia, se si sta testando per undef, davvero non ha senso che qualcosa di indefinito è un numero o una stringa, in modo da avere un operatore solo per questo tipo di test: defined

Puoi test se qualcosa è definito semplicemente facendo

if (defined $foo) { 
    # my cool logic on $foo here 
} 
5

questo potrebbe essere solo la mia opinione, e il tuo stile di codifica è totalmente a voi, ma personalmente trovo un sacco di valore nell'assegnare gli argomenti di variabili destra fuori del blocco , e piuttosto che avvolgere la parte "business" della subroutine in un blocco if, avrei la funzione gracchiare prima e quello Per esempio:

use Carp; 

sub ucwords { 
    my $str = shift; 
    defined($str) 
     or croak 'No overload method of ucwords() takes no arguments'; 
    #the rest of your logic 
} 
+0

Questo è un grande punto. Modificherei l'originale ma il tuo commento sembra inutile. Aggiungerò una modifica ad esso. – NateDSaint

+1

@NateDSaint: Mi sono preso la libertà di modificare il tuo post e cancellare il mio commento. Sentiti libero di tornare indietro se non ti piacciono le modifiche. –

+0

Probabilmente meno fuorviante in questo modo. Grazie! – NateDSaint

4

die

die, come gli altri comandi incorporati Perl, non ha bisogno, e in genere non dovrebbe avere parentesi. Tuttavia, die ha un fratello maggiore che la maggior parte delle persone utilizzano questi giorni, chiamato

croak

Do:

use Carp; 

e poi

croak "My error here!"; 

Croak funziona proprio come morire, ma in generale aggiunge ulteriori informazioni utili al messaggio di errore rispetto a die, come la riga in cui si è verificato l'errore su rel vivo al chiamante.

3

Array indicizzazione

accesso Array, come le altre cose in Perl, è sensibile al contesto. Pensa al sigillo che viene allegato al nome come a un 'promemoria' per te su ciò che stai cercando di accedere o utilizzare al momento. Ogni volta che vedi lo $, significa che stai cercando di ottenere un singolo valore scalare. Ogni volta che vedi un @, significa che stai accedendo a un elenco, e % significa ovviamente una coppia di hash chiave/valore. Quindi, quando si accede alla matrice in questo modo:

@_[1] 

Si sta richiedendo un elenco, contenente un singolo elemento. Questa funzione consente di ottenere più valori da una matrice contemporaneamente, ma quando si accede a un solo valore, si verificano problemi in alcuni contesti, come l'assegnazione. Così, quando si accede a un singolo elemento di un array, si desidera utilizzare sempre il contesto scalare:

$_[1] 
5

istruzione switch di Perl: data/quando

Perl, come di 5,10 e al di sopra, ha un interruttore fantastico dichiarazione incorporata, chiamata [given].Questo è più o meno equivalente alla dichiarazione switch in C, ma molto più versatile. Per attivare questa funzione, è necessario aggiungere una riga nella parte superiore dello script:

use 5.010; 

In questo modo tutte le perl 5.10 caratteristiche, tra cui l'interruttore (e say, che funziona come print ma automaticamente aggiunge un "\ n "alla fine) si può usare in questo modo:.

my $foo = get_foo(); 
my $nothing = 0; 
given($foo) { 
    when (undef) { say "got an undefined value!"; } 
    when ([1,3,5,6,8]) { say "was 1, 3, 5, 6, or 8"; } 
    when (/^abc/) { say "was a string starting with abc"; } 
    when ($_ == 4) { say "It was 4!!!"; } 
    when ($_ > 100) { say "Greater than 100"; } 
    default { $nothing = 1; } 
} 

la variabile passata al dato automaticamente viene messo in $_ all'interno del codice dato, che consente di confrontare contro di essa. Poi, il costrutto when fa un match intelligente contro $_. Quindi, nel tuo caso, sarebbe simile a questa (che fissa il @[] a $ [] rilascio):

given ($_[1]) { 
    when (1) { $str =~ s/\b(\w)/\u$1/g } 
    when (2) { $str = lc($str); $str =~ s/(\w)\b/\u$1/g } 
    when (3) { $str =~ s/(\w)\b/\u$1/g; } 
    default { croak "No overloaded method of ucwords() takes '$_'." } 
} 
5

@_ disimballaggio

In genere, si desidera sempre decomprimere @_ prima di eseguire qualsiasi altra elaborazione nella subroutine. Ciò rende molto, molto più chiaro agli utenti, agli altri manutentori e a te stesso in futuro, su come utilizzare il tuo sub. Usando direttamente @_, è molto difficile capire cosa deve essere passato, solo dagli argomenti forniti. Non hanno alcun nome significativo, rendendo ancora più difficile determinare il loro scopo, e tu hai costanti magiche ovunque - normalmente una cosa negativa nel complesso!

La soluzione migliore è ottenere subito le variabili in scalari con nome significativo, prima di fare qualsiasi altra cosa.

Per le subroutine di un argomento, una soluzione comune è utilizzare shift. Questo estrae il primo elemento di un array e lo restituisce (un po 'come l'opposto di pop). Se non viene fornito un array e ci si trova in una subroutine, esso viene estratto dall'array @_. Quindi, puoi fare

sub mysub { 
    my $foo = shift; 
} 

per una sottoroutine di un argomento.

Tuttavia, cosa succede se ne hai di più? Elenca l'assegnazione del contesto, al salvataggio! È possibile assegnare più variabili contemporaneamente, utilizzando un assegnamento di lista. Si può fare

sub myothersub { 
    my ($foo, $bar, $baz) = @_; 
} 

E $foo, $bar, e $baz verrà assegnato il valore 0, 1, e 2 indici di @_, rispettivamente. Bene, cosa succede se non c'è nulla nell'indice 0, 1 o 2? Vengono ancora assegnati - diventano undef! Quindi puoi controllare undef come menzionato altrove in questa domanda.

2

Mi piacciono molto le funzioni troppo intelligenti. Una funzione eccessivamente intelligente è quella il cui comportamento è totalmente modificato dai suoi parametri. Guardate i vostri, quasi non condividono alcun codice tranne la gestione dei parametri. Comunque se mi sarebbe fare un po 'simile questo vorrei scrivere qualcosa del genere:

use Carp; 

{ 
    my %ucwords = (
     0 => sub { 
      my $str = lc(shift()); 
      $str =~ s/\b(\w)/\u$1/g; 
      return $str; 
     }, 
     1 => sub { 
      my $str = shift; 
      $str =~ s/\b(\w)/\u$1/g; 
      return $str; 
     }, 
     2 => sub { 
      $str = lc(shift()); 
      $str =~ s/(\w)\b/\u$1/g; 
      return $str; 
     }, 
     3 => sub { 
      my $str = shift; 
      $str =~ s/(\w)\b/\u$1/g; 
      return $str; 
     } 
    ); 

    sub ucwords { 
     my ($str, $mode) = @_; 
     croak "No overload method of ucwords() takes no arguments" 
      unless defined $str; 
     $mode = 0 unless defined $mode; 
     my $code = $ucwords{$mode}; 
     croak "Invalid mode: '$mode'" unless defined $code; 
     goto \&$code; 
    } 
} 
2

qualcosa che è stato accennato, ma non direttamente indirizzati in altre risposte è l'uso di modalità numeriche, una convenzione estraneo a Perl rinviato da C Rapido, senza guardare il codice cosa fa la modalità # 3? Diavolo, guardando il codice cosa fa la modalità # 3?

Perl dispone di stringhe efficienti e facili da utilizzare. Usali.Dai ai tuoi modi nomi che hanno qualcosa a che fare con ciò che fa. Qualcosa come ... prima, ultima, recase_first, recase_last. Non devono essere totalmente descrittivi, lower_case_then_uc_last_letter sarebbe troppo lungo per essere digitato, ma abbastanza da dare qualcosa per il cervello umano a cui aggrapparsi e associarsi.

Ma in realtà queste sono quattro subroutine. I flag di modalità sono contrassegni rossi, specialmente quando la maggior parte del codice si trova all'interno di un'istruzione if/else.

Problemi correlati