Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

Boa prática de programação

Pelo que eu sabia, o uso de break em qualquer lugar que não seja um switch era considerada uma má prática de programação por não ser a ideia de uma programação estruturada, nesse caso eu tinha pensado antes que daria pra igualar o i a 4 no caso da resposta estar correta.

O que devo pensar sobre isso?

2 respostas

Olá, Lucas.

No caso da aula, podemos atribuir 4 na variável de iteração e funcionará do mesmo modo.

Mas, será que no futuro esta linha de código terá o mesmo significado? Quando um desenvolvedor for manter um código escrito há anos alterando o a condição de iteração i < 3 para i < 10 ele saberá que é necessário atualizar aquela atribuição escondida dentro de um if ou até 2 ifs aninhados?

O break, apesar de não fazer parte da definição de programação estruturada, não é uma má prática. Ele deixa bastante claro ao desenvolvedor ou desenvolvedora de que "Opa, aqui devemos abandonar o loop!".

O que você acha?

Bons estudos!

solução!

O "problema" com os comandos break e continue estão mais relacionados a um erro de interpretação do célebre artigo Goto considered harmful de Edgar Dijkstra, que propriamente a um problema destes comandos em si.

Quando se escreve programas em assembly, é inevitável escrever estruturas condicionais com jumps. Considere o trecho de código abaixo escrito em assembly MIPS:


             # Seleciona o quadrante ao qual o ângulo pertence

             beq  $t1,$zero,quad1
             addi $t1,$t1,-1
             beq  $t1,$zero,quad2
             addi $t1,$t1,-1
             beq  $t1,$zero,quad3

             quad4: li.s  $f0,1.0
                    s.s   $f0,cSig
                    li.s  $f0,-1.0
                    s.s   $f0,sSig
                    li    $t1,360
                    sub   $t0,$t1,$t0
                    j     toRad

             quad3: li.s  $f0,-1.0
                    s.s   $f0,cSig
                    li.s  $f0,-1.0
                    s.s   $f0,sSig
                    addi  $t0,$t0,-180
                    j     toRad

             quad2: li.s  $f0,-1.0
                    s.s   $f0,cSig
                    li.s  $f0,1.0
                    s.s   $f0,sSig
                    li    $t1,180
                    sub   $t0,$t1,$t0
                    j     toRad

             quad1: li.s  $f0,1.0
                    s.s   $f0,cSig
                    li.s  $f0,1.0
                    s.s   $f0,sSig

             # Transforma o valor do ângulo de graus para radianos

             toRad: mtc1    $t0,$f0
                    cvt.d.w $f0,$f0       
                    s.d     $f0,angle 
                    l.d     $f2,numPi
                    mul.d   $f0,$f0,$f2 
                    li.d    $f2,180.0     
                    s.d     $f0,angle

De forma bastante resumida, esse código pode ser entendido em alto nível como:

switch (valor) {
    case quad4: ... ; break;
    case quad3: ... ; break;
    case quad2: ... ; break;
    case quad1: ... ; 
}

// endereço da primeira instrução após o bloco switch
toRad: ...

Note que o mesmo código poderia ser escrito como:

switch (valor) {
    case quad4: ... ; goto toRad;
    case quad3: ... ; goto toRad;
    case quad2: ... ; goto toRad;
    case quad1: ... ; 
}

// endereço da primeira instrução após o bloco switch
toRad: ...

ou pior ainda

if (valor == quad4) {
    ...
    goto toRad;
}

if (valor == quad3) {
    ...
    goto toRad;
}

if (valor == quad2) {
    ...
    goto toRad;
}

if (valor == quad1) {
    ...
}

// endereço da primeira instrução após o bloco switch
toRad: ...

Se você pegar código Fortran, por exemplo, verá que mesmo em uma linguagem de alto nível era prática comum fazer uso de labels na escrita do código. Era contra este tipo de prática que Dijkstra advoga, devido à tendência de criar código espaguete, o qual é difícil de ler e, consequentemente, manter.

Em sua defesa da programação estruturada, Dijkstra defendia que nossa capacidade de acompanhar processos dinâmicos complexos é, em geral, limitada. Por este motivo, devíamos adotar métodos mais "literários" de programação, que tornem a estrutura de fluxo e o objetivo do código mais transparentes, escondendo aspectos de baixo nível.

No caso específico dos breaks, note que são localizados dentro de suas estruturas de controle e possuem semântica muito clara, quebrar a execução, o que não fere em nada a legibilidade do programa, pelo contrário. É bem verdade que em algumas situações seu uso poderia ser evitado de forma mais elegante pelo uso de uma flag, por exemplo, mas como tudo na programação, não há regra de ouro, depende do caso. Mesmo um goto, bem utilizado, tem seu espaço. Veja o exemplo abaixo:

for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        if (condicaoTerminalPrematura) goto termineAmbos;

        ...
    }
    ...
}
termineAmbos:

Neste exemplo, o goto funciona para quebrar a execução de ambos os laços simultaneamente, sendo que o endereço de salto é localmente próximo, e o nome bastante sugestivo. Tente achar uma solução mais auto-descritiva, e, ouso dizer, elegante, para esse problema.

Em realidade, programação estruturada é muito mais do que usar ou não breaks. Trata-se de escrever código modularizado, com funções pequenas e reutilizáveis; a máxima KISS (Keep it simple, stupid), funções e programas devem fazer uma única coisa, e fazê-la bem feita. Dispor o código de forma mais sequencial possível, de forma que não seja preciso subir e descer para entender o que o programa faz. Evitar fazer uso de artifícios que dificultem o entendimento, por exemplo abusar de side-effects obscuros. Tudo deve ser o mais transparente possível. O objetivo central é descrever o processo de forma lógica e legível.