Mengapa menggunakan trailing baris baru alih-alih memimpin dengan printf?

79

Saya mendengar bahwa Anda harus menghindari memimpin baris baru saat menggunakan printf. Sehingga printf("\nHello World!")sebaiknya Anda gunakanprintf("Hello World!\n")

Dalam contoh khusus di atas tidak masuk akal, karena output akan berbeda, tetapi pertimbangkan ini:

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

dibandingkan dengan:

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

Saya tidak dapat melihat manfaat dengan mengikuti baris baru, kecuali terlihat lebih baik. Apakah ada alasan lain?

SUNTING:

Saya akan membahas suara penutupan di sini dan sekarang. Saya tidak berpikir ini milik Stack overflow, karena pertanyaan ini terutama tentang desain. Saya juga akan mengatakan bahwa meskipun mungkin ada pendapat tentang masalah ini, jawaban Kilian Foth dan jawaban cmaster membuktikan bahwa memang ada manfaat yang sangat objektif dengan satu pendekatan.

klutt
sumber
5
Pertanyaan ini berada di perbatasan antara "masalah dengan kode" (yang di luar topik) dan "desain perangkat lunak konseptual" (yang sesuai topik). Ini mungkin ditutup, tetapi jangan terlalu sulit. Saya pikir menambahkan contoh kode konkret adalah pilihan yang tepat.
Kilian Foth
46
Baris terakhir akan bergabung dengan command prompt di linux tanpa baris baru.
GrandmasterB
4
Jika "terlihat lebih baik" dan tidak memiliki kelemahan, itu alasan yang cukup baik untuk melakukannya, IMO. Menulis kode yang baik tidak ada bedanya dengan menulis novel yang bagus atau makalah teknis yang bagus - iblis selalu detail.
alephzero
5
Apakah init()dan process_data()mencetak sesuatu sendiri? Seperti apa hasil yang Anda harapkan jika itu terjadi?
Bergi
9
\nadalah terminator garis , bukan pemisah garis . Ini dibuktikan oleh fakta bahwa file teks, di UNIX, hampir selalu berakhir \n.
Jonathon Reinhart

Jawaban:

222

Sejumlah I / O terminal cukup buffer-line , jadi dengan mengakhiri pesan dengan \ n Anda dapat yakin bahwa itu akan ditampilkan secara tepat waktu. Dengan petunjuk \ n pesan dapat ditampilkan atau tidak sekaligus. Seringkali, ini berarti bahwa setiap langkah menampilkan pesan progres dari langkah sebelumnya , yang menyebabkan kebingungan tanpa akhir dan membuang waktu ketika Anda mencoba memahami perilaku suatu program.

Kilian Foth
sumber
20
Ini sangat penting ketika menggunakan printf untuk men-debug program yang macet. Menempatkan baris baru di ujung printf berarti stdout ke konsol akan memerah di setiap printf. (Perhatikan bahwa ketika stdout dialihkan ke file, perpustakaan std biasanya akan melakukan blocking buffer bukan line buffering, sehingga membuat printf macet crash cukup sulit bahkan dengan baris baru di akhir.)
Erik Eidt
25
@ErikEidt Perhatikan bahwa Anda sebaiknya menggunakan fprintf(STDERR, …), yang umumnya tidak buffer sama sekali untuk output diagnostik.
Deduplicator
4
@Dupuplikator Menulis pesan diagnostik ke aliran kesalahan juga memiliki kelemahan - banyak skrip menganggap program gagal jika ada sesuatu yang ditulis ke aliran kesalahan.
Voo
54
@ Voo: Saya berpendapat bahwa program apa pun yang menganggap menulis ke stderr menunjukkan kegagalan itu sendiri salah. Kode keluar dari proses adalah yang menunjukkan apakah gagal atau tidak. Jika gagal, maka output stderr akan menjelaskan alasannya . Jika proses keluar dengan sukses (kode keluar nol) maka output stderr harus dianggap sebagai output informasi untuk pengguna manusia, tanpa semantik parseable mesin tertentu (misalnya, mungkin berisi peringatan yang dapat dibaca manusia, misalnya), sedangkan stdout adalah output aktual dari program, mungkin cocok untuk diproses lebih lanjut.
Daniel Pryden
23
@ Voo: Program apa yang Anda gambarkan? Saya tidak mengetahui adanya paket perangkat lunak yang digunakan secara luas yang berperilaku seperti yang Anda gambarkan. Saya tahu bahwa ada program yang melakukannya, tetapi tidak seperti saya hanya membuat konvensi yang saya jelaskan di atas: itulah cara sebagian besar program di lingkungan Unix atau Unix-like bekerja, dan setahu saya, cara sebagian besar program selalu ada. Saya tentu tidak akan menganjurkan untuk program apa pun untuk menghindari menulis ke stderr hanya karena beberapa skrip tidak menanganinya dengan baik.
Daniel Pryden
73

Pada sistem POSIX (pada dasarnya setiap linux, BSD, sistem berbasis open source apa pun yang dapat Anda temukan), sebuah baris didefinisikan sebagai serangkaian karakter yang diakhiri oleh baris baru \n. Ini adalah asumsi dasar semua alat baris perintah standar membangun, termasuk (namun tidak terbatas pada) wc, grep, sed, awk, dan vim. Ini juga alasan mengapa beberapa editor (seperti vim) selalu menambahkan a \ndi akhir file, dan mengapa standar C sebelumnya mengharuskan header untuk diakhiri dengan \nkarakter.

Btw: Setelah \nmengakhiri baris membuat pemrosesan teks lebih mudah: Anda tahu pasti bahwa Anda memiliki baris yang lengkap ketika Anda memiliki terminator itu. Dan Anda tahu pasti bahwa Anda perlu melihat lebih banyak karakter jika Anda belum menemukan terminator itu.

Tentu saja, ini ada di sisi input program, tetapi output program sangat sering digunakan sebagai input program lagi. Jadi, output Anda harus tetap pada konvensi demi memungkinkan masukan tanpa batas ke program lain.

cmaster
sumber
25
Ini adalah salah satu perdebatan tertua dalam rekayasa perangkat lunak: apakah lebih baik menggunakan baris baru (atau, dalam bahasa pemrograman, penanda "akhir pernyataan" lainnya seperti titik koma) sebagai terminator garis atau pemisah baris ? Kedua pendekatan memiliki pro dan kontra. Dunia Windows sebagian besar bersandar pada gagasan bahwa urutan baris baru (yang biasanya CR LF di sana) adalah pemisah baris , dan dengan demikian baris terakhir dalam file tidak perlu diakhiri dengan itu. Di dunia Unix, meskipun, urutan baris baru (LF) adalah terminator garis , dan banyak program dibangun di sekitar asumsi ini.
Daniel Pryden
33
POSIX bahkan mendefinisikan garis sebagai "Urutan nol atau lebih karakter non-baris baru plus karakter baris baru yang berakhir ."
pipa
6
Mengingat bahwa seperti yang dikatakan @pipe, ini ada dalam spesifikasi POSIX, kita mungkin dapat menyebutnya de jure sebagai lawan de facto seperti jawabannya?
Baldrickk
4
@ Baldrickk Benar. Saya telah memperbarui jawaban saya menjadi lebih tegas, sekarang.
cmaster
C juga membuat konvensi ini untuk file sumber: file sumber tidak kosong yang tidak diakhiri dengan baris baru menghasilkan perilaku yang tidak terdefinisi.
R ..
31

Selain apa yang disebutkan orang lain, saya merasa ada alasan yang lebih sederhana: itu standar. Setiap kali sesuatu dicetak ke STDOUT, hampir selalu mengasumsikan bahwa itu sudah ada di baris baru, dan dengan demikian tidak perlu memulai yang baru. Itu juga mengasumsikan baris berikutnya yang akan ditulis akan bertindak dengan cara yang sama, sehingga membantu mengakhiri dengan memulai baris baru.

Jika Anda menampilkan garis baris baru di awal yang disatukan dengan garis trailing-baris baru standar, "itu akan berakhir seperti ini:

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

... yang mungkin bukan yang Anda inginkan.

Jika Anda hanya menggunakan baris baru yang memimpin dalam kode Anda dan hanya menjalankannya dalam IDE, itu mungkin akan baik-baik saja. Segera setelah Anda menjalankannya di terminal atau memperkenalkan kode orang lain yang akan menulis ke STDOUT di samping kode Anda, Anda akan melihat output yang tidak diinginkan seperti di atas.

The Guy with The Hat
sumber
2
Hal serupa terjadi dengan program yang terputus dalam shell interaktif - jika sebagian garis dicetak (tidak ada baris baru), maka shell menjadi bingung tentang kolom mana kursor aktif, sehingga sulit untuk mengedit baris perintah berikutnya. Kecuali jika Anda menambahkan baris baru terkemuka ke Anda $PS1, yang kemudian akan mengganggu program konvensional.
Toby Speight
17

Karena jawaban yang sangat tinggi telah memberikan alasan teknis yang sangat baik mengapa mengikuti baris baru harus lebih disukai, saya akan mendekatinya dari sudut lain.

Menurut pendapat saya, berikut ini membuat program lebih mudah dibaca:

  1. rasio signal-to-noise yang tinggi (alias sederhana tetapi tidak sederhana)
  2. ide-ide penting diutamakan

Dari poin di atas, kita dapat berargumen bahwa mengikuti baris baru lebih baik. Baris baru memformat "noise" bila dibandingkan dengan pesan, pesan harus menonjol dan karenanya harus didahulukan (penyorotan sintaks juga dapat membantu).

Alex Vong
sumber
19
Ya, "ok\n"jauh lebih baik daripada "\nok"...
cmaster
@ cmaster: Mengingatkan saya telah membaca tentang MacOS menggunakan Pascal-string APIs di C, yang mengharuskan awalan semua string literal dengan kode seperti escape magic "\pFoobar".
grawity
16

Menggunakan trailing newlines menyederhanakan modifikasi selanjutnya.

Sebagai contoh (sangat sepele) berdasarkan kode OP, misalkan Anda perlu menghasilkan beberapa output sebelum pesan "Inisialisasi", dan output tersebut berasal dari bagian logis yang berbeda dari kode, dalam file sumber yang berbeda.

Ketika Anda menjalankan tes pertama dan menemukan "Inisialisasi" sekarang ditambahkan ke akhir baris dari beberapa output lain, Anda harus mencari melalui kode untuk menemukan di mana ia dicetak, dan kemudian berharap mengubah "Inisialisasi" menjadi "\ nInisialisasi "Tidak mengacaukan format sesuatu yang lain, dalam keadaan yang berbeda.

Sekarang pertimbangkan bagaimana menangani fakta bahwa output baru Anda sebenarnya opsional, jadi perubahan Anda ke "\ nInitializing" terkadang menghasilkan baris kosong yang tidak diinginkan pada awal output ...

Apakah Anda menetapkan bendera global ( guncangan horor ?? !!! ) yang mengatakan apakah ada keluaran sebelumnya dan mengujinya untuk mencetak "Inisialisasi" dengan memimpin opsional "\ n", atau apakah Anda menampilkan "\ n" bersama dengan output Anda sebelumnya dan meninggalkan pembaca kode masa depan bertanya-tanya mengapa "Inisialisasi" ini tidak memiliki "" memimpin "seperti semua pesan output lainnya lakukan?

Jika Anda secara konsisten menampilkan trailing baris baru, pada titik di mana Anda tahu Anda telah mencapai ujung garis yang perlu dihentikan, Anda menghindari semua masalah itu. Catatan, yang mungkin memerlukan pernyataan put ("\ n") yang terpisah di akhir beberapa logika yang menghasilkan sepotong demi sepotong, tetapi intinya adalah Anda menampilkan baris baru di tempat paling awal dalam kode di mana Anda tahu harus lakukan itu, bukan di tempat lain.

alephzero
sumber
1
Jika setiap item output independen seharusnya muncul pada barisnya sendiri, mengekor baris baru dapat berfungsi dengan baik. Jika beberapa item harus dikonsolidasikan ketika praktis, namun, hal-hal menjadi lebih rumit. Jika praktis untuk memberi makan semua output melalui rutin umum, operasi untuk menyisipkan garis yang jelas ke ujung jika karakter terakhir adalah CR, tidak ada apa-apa jika karakter terakhir adalah baris baru, dan baris baru jika karakter terakhir adalah hal lain, semoga bermanfaat jika program perlu melakukan sesuatu selain mengeluarkan urutan jalur independen.
supercat
7

Mengapa menggunakan trailing baris baru alih-alih memimpin dengan printf?

Sangat cocok dengan spesifikasi C.

Pustaka C mendefinisikan garis sebagai diakhiri dengan karakter baris baru '\n' .

Aliran teks adalah urutan karakter yang tersusun dalam baris , setiap baris terdiri dari nol atau lebih karakter plus karakter baris baru yang berakhir. Apakah baris terakhir memerlukan pemutusan karakter baris baru ditentukan implementasi. C11 §7.21.2 2

Kode yang menulis data sebagai baris kemudian akan cocok dengan konsep perpustakaan itu.

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: baris terakhir membutuhkan pemutusan karakter baris baru . Saya akan merekomendasikan untuk selalu menulis final '\n'pada output dan mentolerir ketidakhadiran pada input.


Pemeriksaan ejaan

Pemeriksa ejaan saya mengeluh. Mungkin Anda juga.

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

 v--------v OK
"Processing\n");
chux
sumber
Saya pernah memperbaiki ispell.eluntuk mengatasi dengan lebih baik. Saya akui itu lebih sering \tmenjadi masalah, dan itu bisa dihindari hanya dengan memecah string menjadi beberapa token, tapi itu hanya efek samping dari pekerjaan "abaikan" yang lebih umum, untuk secara selektif melewati bagian non-teks dari HTML atau badan multi bagian MIME, dan bagian kode yang tidak dikomentari. Saya selalu bermaksud memperluasnya ke peralihan bahasa di mana ada metadata yang sesuai (misalnya <p lang="de_AT">atau Content-Language: gd), tetapi tidak pernah mendapatkan Round Tuit. Dan pengelola langsung menolak patch saya. :-(
Toby Speight
@TobySpeight Ini dia putaran . Nantikan mencoba perbaikan Anda ispell.el.
chux
4

Memimpin baris baru sering kali dapat membuatnya lebih mudah untuk menulis kode ketika ada persyaratan, misalnya,

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

(Tetapi seperti yang telah dicatat di tempat lain Anda mungkin perlu menyiram buffer output sebelum melakukan langkah-langkah yang membutuhkan banyak waktu CPU.)

Oleh karena itu kasus yang baik dapat dibuat untuk kedua cara melakukannya, namun secara pribadi saya tidak suka printf () dan akan menggunakan kelas khusus untuk membangun output.

Ian
sumber
1
Bisakah Anda menjelaskan mengapa versi ini lebih mudah untuk ditulis daripada versi yang mengikuti baris baru? Dalam contoh ini tidak tampak bagi saya. Sebaliknya saya bisa melihat masalah yang timbul dengan output berikutnya yang ditambahkan ke baris yang sama "\nProcessing".
Raimund Krämer
Seperti Raimund, saya juga bisa melihat masalah yang timbul dari bekerja seperti ini. Anda perlu mempertimbangkan cetakan di sekitarnya saat menelepon printf. Bagaimana jika Anda ingin mengkondisikan seluruh baris "Inisialisasi"? Anda harus memasukkan baris "Memproses" dalam kondisi itu untuk mengetahui apakah Anda harus mengawali dengan baris baru atau tidak. Jika ada cetakan lain di depan dan Anda perlu mengkondisikan baris "Memproses", Anda juga perlu menyertakan cetakan berikutnya dalam kondisi itu untuk mengetahui apakah Anda harus mengawali dengan baris baru lainnya, dan sebagainya untuk setiap cetakan.
JoL
2
Saya setuju dengan prinsipnya, tetapi contohnya tidak bagus. Contoh yang lebih relevan adalah dengan kode yang seharusnya menampilkan sejumlah item per baris. Jika output seharusnya dimulai dengan header yang berakhir dengan baris baru, dan setiap baris seharusnya dimulai dengan header, mungkin lebih mudah untuk mengatakan, misalnya if ((addr & 0x0F)==0) printf("\n%08X:", addr);dan tanpa syarat menambahkan baris baru ke output di akhir, daripada menggunakan kode terpisah untuk setiap tajuk baris dan baris tambahan.
supercat
1

Baris baru terkemuka tidak berfungsi dengan baik dengan fungsi perpustakaan lain, terutama puts()dan perrordi Perpustakaan Standar, tetapi juga perpustakaan lain yang mungkin Anda gunakan.

Jika Anda ingin mencetak baris pra-tertulis (baik konstanta, atau yang sudah diformat - misalnya dengan sprintf()), maka puts()adalah pilihan alami (dan efisien). Namun, tidak ada cara untuk puts()mengakhiri baris sebelumnya dan menulis baris yang tidak ditentukan - selalu menulis terminator garis.

Toby Speight
sumber