Por que usar novas linhas à direita em vez de liderar com printf?

79

Ouvi dizer que você deve evitar liderar novas linhas ao usar printf. Para que, em vez de printf("\nHello World!")você, useprintf("Hello World!\n")

Neste exemplo em particular acima, não faz sentido, pois a saída seria diferente, mas considere isso:

printf("Initializing");
init();
printf("\nProcessing");
process_data();
printf("\nExiting");

comparado com:

printf("Initializing\n");
init();
printf("Processing\n");
process_data();
printf("Exiting");

Não vejo nenhum benefício em seguir as novas linhas, exceto que parece melhor. Existe algum outro motivo?

EDITAR:

Vou abordar os votos próximos aqui e agora. Eu não acho que isso pertença ao estouro de pilha, porque esta pergunta é principalmente sobre design. Também gostaria de dizer que, embora possa ser opiniões a este assunto, a resposta de Kilian Foth e resposta de cmaster prova que há de fato benefícios muito objetivas com uma abordagem.

Klutt
fonte
5
Esta questão está na fronteira entre "problemas com código" (que não é tópico) e "design conceitual de software" (que é tópico). Pode ficar fechado, mas não leve muito a sério. Eu acho que adicionar exemplos concretos de código foi a escolha certa, no entanto.
Kilian Foth
46
A última linha seria mesclada com o prompt de comando no linux sem uma nova linha à direita.
GrandmasterB
4
Se "parece melhor" e não tem desvantagem, é uma razão suficientemente boa para fazê-lo, IMO. Escrever um bom código não é diferente de escrever um bom romance ou um bom artigo técnico - o diabo está sempre nos detalhes.
191118 Alephzero
5
Faça init()e process_data()imprima alguma coisa? Como você esperaria que o resultado se aparecesse?
Bergi 19/11/19
9
\né um terminador de linha , não um separador de linha . Isso é evidenciado pelo fato de que os arquivos de texto, no UNIX, quase sempre terminam \n.
Jonathon Reinhart

Respostas:

222

Uma quantidade razoável de E / S do terminal é armazenada em buffer de linha ; portanto, encerrando uma mensagem com \ n, você pode ter certeza de que ela será exibida em tempo hábil. Com um \ n inicial, a mensagem pode ou não ser exibida de uma só vez. Freqüentemente, isso significa que cada etapa exibe a mensagem de progresso da etapa anterior , o que não causa confusão e desperdiça tempo ao tentar entender o comportamento de um programa.

Kilian Foth
fonte
20
Isso é particularmente importante ao usar o printf para depurar um programa que trava. Colocar a nova linha no final de um printf significa que o stdout no console é liberado a cada printf. (Note que quando stdout é redirecionada para um arquivo, as bibliotecas std geralmente não bloquear o buffer em vez de linha de tamponamento, de modo que faz printf depuração de um acidente bastante difícil, mesmo com nova linha no final.)
Erik Eidt
25
@ErikEidt Observe que você deve usar fprintf(STDERR, …), em geral, que geralmente não é armazenado em buffer para saída de diagnóstico.
Deduplicator
4
@Duplicator A gravação de mensagens de diagnóstico no fluxo de erros também tem suas desvantagens - muitos scripts supõem que um programa falhou se algo foi gravado no fluxo de erros.
Voo
54
@ Voo: Eu diria que qualquer programa que assume gravações no stderr indica que uma falha está incorreta. O código de saída do processo é o que indica se falhou ou não. Se houve uma falha, a saída stderr explicará o motivo . Se o processo foi concluído com êxito (código de saída zero), a saída stderr deve ser considerada uma saída informativa para um usuário humano, sem uma semântica específica para a máquina (pode conter avisos legíveis por humanos, por exemplo), enquanto stdout é a saída real de o programa, possivelmente adequado para processamento adicional.
Daniel Pryden
23
@Voo: Quais programas você está descrevendo? Não conheço nenhum pacote de software amplamente usado que se comporte como você descreve. Eu sei que existem programas que fazem isso, mas não é como se eu tivesse acabado de criar a convenção que descrevi acima: é assim que a grande maioria dos programas em um ambiente Unix ou Unix-like funciona, e que eu saiba, é a maneira como o grande maioria dos programas sempre tem. Eu certamente não defenderia nenhum programa para evitar escrever no stderr simplesmente porque alguns scripts não lidam bem com isso.
Daniel Pryden
73

Nos sistemas POSIX (basicamente qualquer linux, BSD, qualquer sistema baseado em código aberto que você puder encontrar), uma linha é definida como uma sequência de caracteres terminados por uma nova linha \n. Este é o pressuposto básico de todas as ferramentas de linha de comando padrão construir em cima, incluindo (mas não limitado a) wc, grep, sed, awk, e vim. Essa também é a razão pela qual algum editor (como vim) sempre adiciona um \nno final de um arquivo e por que os padrões anteriores de C exigiam que os cabeçalhos terminassem com um \ncaractere.

Btw: Ter \nlinhas finalizadas facilita muito o processamento de texto: você tem certeza de que possui uma linha completa quando possui esse terminador. E você tem certeza de que precisa procurar mais personagens se ainda não encontrou esse terminador.

Obviamente, isso ocorre no lado de entrada dos programas, mas a saída do programa é frequentemente usada como entrada de programa novamente. Portanto, sua saída deve seguir a convenção, a fim de permitir uma entrada direta em outros programas.

cmaster
fonte
25
Este é um dos debates mais antigos em engenharia de software: é melhor usar novas linhas (ou, em uma linguagem de programação, outro marcador de "fim de declaração" como ponto e vírgula) como terminadores ou separadores de linhas ? Ambas as abordagens têm seus prós e contras. O mundo do Windows se baseou principalmente na idéia de que a sequência de nova linha (que normalmente é CR LF) é um separador de linhas e, portanto, a última linha de um arquivo não precisa terminar com ela. No mundo Unix, porém, uma sequência de nova linha (LF) é um terminador de linha , e muitos programas são construídos em torno dessa suposição.
Daniel Pryden
33
O POSIX até define uma linha como "Uma sequência de zero ou mais caracteres não-nova linha mais um caractere final de nova linha ".
pipe
6
Dado que, como o @pipe diz, está na especificação POSIX, provavelmente podemos chamá-lo de jure em oposição a de fato, como a resposta sugere?
Baldrickk
4
@Baldrickk Right. Atualizei minha resposta para ser mais afirmativa agora.
Cmaster #
C também cria esta convenção para arquivos de origem: um arquivo de origem não vazio que não termina com uma nova linha produz um comportamento indefinido.
R ..
31

Além do que outros já mencionaram, sinto que há uma razão muito mais simples: é o padrão. Sempre que algo é impresso em STDOUT, quase sempre assume que ele está em uma nova linha e, portanto, não precisa iniciar uma nova. Ele também pressupõe que a próxima linha a ser escrita funcionará da mesma maneira e, portanto, termina de maneira útil iniciando uma nova linha.

Se você produzir linhas de linha de frente principais entrelaçadas com linhas de linha de fuga à linha padrão ", ela terá a seguinte aparência:

Trailing-newline-line
Trailing-newline-line

Leading-newline-line
Leading-newline-line
Leading-newline-lineTrailing-newline-line
Trailing-newline-line

Leading-newline-lineTrailing-newline-line
Trailing-newline-line
Trailing-newline-line

... o que presumivelmente não é o que você deseja.

Se você usar apenas as novas linhas principais no seu código e executá-lo apenas em um IDE, pode ser que isso aconteça. Assim que você executá-lo em um terminal ou introduzir o código de outras pessoas que gravará no STDOUT ao lado do seu código, você verá resultados indesejáveis, como acima.

O cara com o chapéu
fonte
2
Algo semelhante acontece com os programas interrompidos em um shell interativo - se uma linha parcial é impressa (sem a nova linha), o shell fica confuso sobre em qual coluna o cursor está, dificultando a edição da próxima linha de comando. A menos que você adicione uma nova linha principal à sua $PS1, o que seria irritante após os programas convencionais.
Toby Speight
17

Como as respostas altamente votadas já deram excelentes razões técnicas pelas quais as novas linhas finais devem ser preferidas, abordarei isso de outro ângulo.

Na minha opinião, os itens a seguir tornam um programa mais legível:

  1. uma alta relação sinal / ruído (também conhecida como simples, mas não mais simples)
  2. idéias importantes vêm em primeiro lugar

Pelos pontos acima, podemos argumentar que as novas linhas à direita são melhores. As novas linhas estão formatando "ruído" quando comparadas à mensagem, a mensagem deve se destacar e, portanto, deve vir em primeiro lugar (o destaque da sintaxe também pode ajudar).

Alex Vong
fonte
19
Sim, "ok\n"é muito melhor do que "\nok"...
cmaster
@cmaster: Lembra-me de ter lido sobre MacOS usando APIs Pascal cordas em C, o que exigiu predefinir todas as strings literais com um código de escape magia como "\pFoobar".
grawity
16

O uso de novas linhas finais simplifica as modificações posteriores.

Como um exemplo (muito trivial) baseado no código do OP, suponha que você precise produzir alguma saída antes da mensagem "Inicializando", e essa saída venha de uma parte lógica diferente do código, em um arquivo de origem diferente.

Quando você executa o primeiro teste e descobre que "Inicializando" agora é anexado ao final de uma linha de alguma outra saída, você precisa pesquisar o código para descobrir onde ele foi impresso e depois esperar alterar "Inicializando" para "\ nInicializar "não estraga o formato de outra coisa, em circunstâncias diferentes.

Agora, considere como lidar com o fato de que sua nova saída é realmente opcional; portanto, sua alteração em "\ nInicialização" às vezes produz uma linha em branco indesejada no início da saída ...

Você define um sinalizador global ( horror de choque ?? !!! ) dizendo se havia alguma saída anterior e o testa para imprimir "Inicializando" com um \ n "\ n" inicial opcional, ou você gera o "\ n" junto com sua saída anterior e deixar futuros leitores de código se perguntando por que essa "Inicialização" não tem um \ n "\ n" à esquerda, como todas as outras mensagens de saída?

Se você produzir consistentemente novas linhas finais, no ponto em que você sabe que atingiu o final da linha que precisa ser encerrada, evite todos esses problemas. Observe que isso pode exigir uma declaração de puts separada ("\ n") no final de alguma lógica que produza uma linha peça por peça, mas o ponto é que você gera a nova linha no primeiro local do código em que você sabe que precisa faça isso, não em outro lugar.

alephzero
fonte
1
Se todo item de saída independente deve aparecer em sua própria linha, novas linhas à direita podem funcionar bem. Se vários itens devem ser consolidados quando praticados, no entanto, as coisas ficam mais complicadas. Se for prático alimentar toda a saída através de uma rotina comum, uma operação para inserir uma linha clara até o final da linha se o último caractere for um CR, nada se o último caractere for uma nova linha e uma nova linha se o último caractere era qualquer outra coisa, pode ser útil se os programas precisarem fazer algo diferente de gerar uma sequência de linhas independentes.
Supercat #
7

Por que usar novas linhas à direita em vez de liderar com printf?

Corresponder de perto à especificação C.

A biblioteca C define uma linha como terminando com um caractere de nova linha '\n' .

Um fluxo de texto é uma sequência ordenada de caracteres compostos em linhas , cada linha consistindo em zero ou mais caracteres mais um caractere de nova linha final. Se a última linha requer um caractere de nova linha final é definida pela implementação. C11 §7.21.2 2

O código que grava dados como linhas corresponderá a esse conceito de biblioteca.

printf("Initializing"); // Write part of a line
printf("\nProcessing"); // Finish prior line & write part of a line
printf("\nExiting");    // Finish prior line & write an implementation-defined last line

printf("Initializing\n");//Write a line 
printf("Processing\n");  //Write a line
printf("Exiting");       //Write an implementation-defined last line

Re: última linha requer um caractere de nova linha final . Eu recomendo sempre escrever uma final '\n'na saída e tolerar sua ausência na entrada.


Verificação ortográfica

Meu verificador ortográfico reclama. Talvez o seu também.

  v---------v Not a valid word
"\nProcessing"

 v--------v OK
"Processing\n");
chux
fonte
Uma vez eu melhorei ispell.elpara lidar melhor com isso. Admito \tque o problema era mais frequente , e isso poderia ser evitado simplesmente dividindo a sequência em vários tokens, mas era apenas um efeito colateral de um trabalho mais geral de "ignorar", para ignorar seletivamente partes não-texto do HTML ou corpos MIME de várias partes e partes de código que não sejam comentários. Eu sempre quis estendê-lo para alternar idiomas onde existem metadados apropriados (por exemplo, <p lang="de_AT">ou Content-Language: gd), mas nunca recebi um Round Tuit. E o mantenedor rejeitou meu patch completamente. :-(
Toby Speight
@TobySpeight Aqui está uma toit rodada . Ansiosos para tentar seu re-aprimorado ispell.el.
Chux #
4

As novas linhas principais geralmente tornam mais fácil escrever o código quando há condicionais, por exemplo,

printf("Initializing");
if (jobName != null)
    printf(": %s", jobName);
init();
printf("\nProcessing");

(Mas, como foi observado em outro lugar, pode ser necessário liberar o buffer de saída antes de executar as etapas que levam muito tempo na CPU.)

Portanto, um bom argumento pode ser feito para as duas formas de fazê-lo, por mais que eu pessoalmente não goste de printf () e usaria uma classe personalizada para criar a saída.

Ian
fonte
1
Você poderia explicar por que essa versão é mais fácil de escrever do que uma com novas linhas à direita? Neste exemplo, não é aparente para mim. Em vez disso, pude ver problemas surgindo com a próxima saída sendo adicionada à mesma linha que "\nProcessing".
Raimund Krämer
Como Raimund, também vejo problemas decorrentes do trabalho como esse. Você deve considerar as impressões ao redor ao ligar printf. E se você quisesse condicionalizar toda a linha "Inicializando"? Você precisaria incluir a linha "Procesing" nessa condição para saber se deveria prefixar uma nova linha ou não. Se houver outra impressão à frente e você precisar condicionar a linha "Processando", também precisará incluir a próxima impressão nessa condição para saber se deve prefixar outra linha de nova e assim por diante para cada impressão.
JOL
2
Eu concordo com o princípio, mas o exemplo não é bom. Um exemplo mais relevante seria o código que deve gerar algum número de itens por linha. Se a saída deve começar com um cabeçalho que termina com uma nova linha e cada linha deve começar com um cabeçalho, pode ser mais fácil dizer, por exemplo, if ((addr & 0x0F)==0) printf("\n%08X:", addr);e adicionar incondicionalmente uma nova linha à saída no final, do que usar código separado para o cabeçalho de cada linha e a nova linha à direita.
Supercat
1

Novas linhas principais não funcionam bem com outras funções de biblioteca, nomeadamente puts()e perrorna biblioteca padrão, mas também qualquer outra biblioteca é provável que você usar.

Se você deseja imprimir uma linha pré-escrita (constante, ou já formatada - por exemplo, com sprintf()), então puts()é a opção natural (e eficiente). No entanto, não há como puts()terminar a linha anterior e escrever uma linha não terminada - ela sempre grava o terminador da linha.

Toby Speight
fonte