The Enterprise

Silence is golden

Michael Pankov •

You surely have heard this quote already:
Silence is golden.
It's often used in context of human relations. A slightly less known formulation of the thought is this:
Rule of Silence: Developers should design programs so that they do not print unnecessary output. This rule aims to allow other programs and developers to pick out the information they need from a program's output without having to parse verbosity.
This is the one of the design rules of Unix philosophy.



Let's consider an example of copying a file on a typical *nix system:
# cp source target
#
 
The program doesn't output anything. It just silently does its' job and quits. And it does so even if the file is several gigabytes in size and it takes some minutes to copy it.

What's good about it?

Most importantly, this principle emphasizes the value of user's attention: a program shouldn't distract the person launching it with unnecessary messages. If everything goes "as usual", it should not print anything — even progress messages for long-running operations. Then it's easy to spot irregularities in the output. Well, spotting any output is certainly easier, when there's none for successful completion.

For example, when an error occurs, the output appears, making it clear something is out-of-order:
# cp nonexistent target
cp: cannot stat ‘nonexistent’: No such file or directory
#
This way you don't have to examine the return code of the program to know if it was successful.

The principle also means it's easy to sequence programs together to do a complex task. Let's consider an example of getting sizes of all the files in the directory. (I know there are other ways, which might be considered more optimal. This is just to explain what I'm talking about).
# ls -l | awk '{print $5}'

4096
307957
47399
14384
4096
4096
#
What would happen if ls would explain what it's doing first, like this:
# ls -l
listing all files in the directory
...

ls -l | awk '{print $5}'
the
4096
...
Notice the "the". That's the thing. Now it's obviously silly to try to pass the sizes to some other program which would like numbers as its' input — "the" spoils the plan.

Arguably the annotated output is easier to understand for non-familiar user — but well, to run the command the user had to know what does -l switch do in the first place.

Now to whose who do violate the rule. For starters, there's GNU Make. Yes, this example output is long. I'm not hiding it on purpose. Go ahead, scroll it.
➜  redis git:(464fef9) make -j8
cd src && make all
make[1]: Entering directory `/home/constantius/redis/src'
rm -rf redis-server redis-sentinel redis-cli redis-benchmark redis-check-dump redis-check-aof *.o *.gcda *.gcno *.gcov redis.info lcov-html
(cd ../deps && make distclean)
make[2]: Entering directory `/home/constantius/redis/deps'
(cd hiredis && make clean) > /dev/null || true
(cd linenoise && make clean) > /dev/null || true
(cd lua && make clean) > /dev/null || true
(cd jemalloc && [ -f Makefile ] && make distclean) > /dev/null || true
(rm -f .make-*)
make[2]: Leaving directory `/home/constantius/redis/deps'
(rm -f .make-*)
echo STD=-std=c99 -pedantic >> .make-settings
echo WARN=-Wall >> .make-settings
echo OPT=-O2 >> .make-settings
echo MALLOC=jemalloc >> .make-settings
echo CFLAGS= >> .make-settings
echo LDFLAGS= >> .make-settings
echo REDIS_CFLAGS= >> .make-settings
echo REDIS_LDFLAGS= >> .make-settings
echo PREV_FINAL_CFLAGS=-std=c99 -pedantic -Wall -O2 -g -ggdb   -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -DUSE_JEMALLOC -I../deps/jemalloc/include >> .make-settings
echo PREV_FINAL_LDFLAGS=  -g -ggdb -rdynamic >> .make-settings
(cd ../deps && make hiredis linenoise lua jemalloc)
make[2]: Entering directory `/home/constantius/redis/deps'
(cd hiredis && make clean) > /dev/null || true
(cd linenoise && make clean) > /dev/null || true
(cd lua && make clean) > /dev/null || true
(cd jemalloc && [ -f Makefile ] && make distclean) > /dev/null || true
(rm -f .make-*)
(echo "" > .make-ldflags)
(echo "" > .make-cflags)
MAKE hiredis
MAKE linenoise
cd hiredis && make static
MAKE lua
cd linenoise && make
cd lua/src && make all CFLAGS="-O2 -Wall -DLUA_ANSI " MYLDFLAGS=""
MAKE jemalloc
cd jemalloc && ./configure --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="-std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops " LDFLAGS=""
make[3]: Entering directory `/home/constantius/redis/deps/linenoise'
cc  -Wall -Os -g  -c linenoise.c
make[3]: Entering directory `/home/constantius/redis/deps/lua/src'
cc -O2 -Wall -DLUA_ANSI    -c -o lapi.o lapi.c
cc -O2 -Wall -DLUA_ANSI    -c -o lcode.o lcode.c
make[3]: Entering directory `/home/constantius/redis/deps/hiredis'
cc -std=c99 -pedantic -c -O3 -fPIC  -Wall -W -Wstrict-prototypes -Wwrite-strings -g -ggdb  net.c
cc -std=c99 -pedantic -c -O3 -fPIC  -Wall -W -Wstrict-prototypes -Wwrite-strings -g -ggdb  hiredis.c
cc -O2 -Wall -DLUA_ANSI    -c -o ldebug.o ldebug.c
cc -O2 -Wall -DLUA_ANSI    -c -o ldo.o ldo.c
cc -O2 -Wall -DLUA_ANSI    -c -o ldump.o ldump.c
cc -std=c99 -pedantic -c -O3 -fPIC  -Wall -W -Wstrict-prototypes -Wwrite-strings -g -ggdb  sds.c
cc -O2 -Wall -DLUA_ANSI    -c -o lfunc.o lfunc.c
cc -O2 -Wall -DLUA_ANSI    -c -o lgc.o lgc.c
make[3]: Leaving directory `/home/constantius/redis/deps/linenoise'
cc -O2 -Wall -DLUA_ANSI    -c -o llex.o llex.c
cc -O2 -Wall -DLUA_ANSI    -c -o lmem.o lmem.c
cc -O2 -Wall -DLUA_ANSI    -c -o lobject.o lobject.c
cc -O2 -Wall -DLUA_ANSI    -c -o lopcodes.o lopcodes.c
cc -O2 -Wall -DLUA_ANSI    -c -o lparser.o lparser.c
cc -O2 -Wall -DLUA_ANSI    -c -o lstate.o lstate.c
cc -O2 -Wall -DLUA_ANSI    -c -o lstring.o lstring.c
cc -O2 -Wall -DLUA_ANSI    -c -o ltable.o ltable.c
cc -std=c99 -pedantic -c -O3 -fPIC  -Wall -W -Wstrict-prototypes -Wwrite-strings -g -ggdb  async.c
cc -O2 -Wall -DLUA_ANSI    -c -o ltm.o ltm.c
cc -O2 -Wall -DLUA_ANSI    -c -o lundump.o lundump.c
cc -O2 -Wall -DLUA_ANSI    -c -o lvm.o lvm.c
cc -O2 -Wall -DLUA_ANSI    -c -o lzio.o lzio.c
cc -O2 -Wall -DLUA_ANSI    -c -o strbuf.o strbuf.c
checking for xsltproc... no
checking for gcc... gcc
cc -O2 -Wall -DLUA_ANSI    -c -o lauxlib.o lauxlib.c
cc -O2 -Wall -DLUA_ANSI    -c -o lbaselib.o lbaselib.c
cc -O2 -Wall -DLUA_ANSI    -c -o ldblib.o ldblib.c
checking whether the C compiler works... cc -O2 -Wall -DLUA_ANSI    -c -o liolib.o liolib.c
yes
checking for C compiler default output file name... a.out
checking for suffix of executables... cc -O2 -Wall -DLUA_ANSI    -c -o lmathlib.o lmathlib.c
cc -O2 -Wall -DLUA_ANSI    -c -o loslib.o loslib.c

checking whether we are cross compiling... ar rcs libhiredis.a net.o hiredis.o sds.o async.o
cc -O2 -Wall -DLUA_ANSI    -c -o ltablib.o ltablib.c
cc -O2 -Wall -DLUA_ANSI    -c -o lstrlib.o lstrlib.c
make[3]: Leaving directory `/home/constantius/redis/deps/hiredis'
cc -O2 -Wall -DLUA_ANSI    -c -o loadlib.o loadlib.c
cc -O2 -Wall -DLUA_ANSI    -c -o linit.o linit.c
no
cc -O2 -Wall -DLUA_ANSI    -c -o lua_cjson.o lua_cjson.c
checking for suffix of object files... cc -O2 -Wall -DLUA_ANSI    -c -o lua_struct.o lua_struct.c
cc -O2 -Wall -DLUA_ANSI    -c -o lua_cmsgpack.o lua_cmsgpack.c
o
checking whether we are using the GNU C compiler... lua_cmsgpack.c: In function ‘table_is_an_array’:
lua_cmsgpack.c:370:21: warning: variable ‘max’ set but not used [-Wunused-but-set-variable]
     long count = 0, max = 0, idx = 0;
                     ^
cc -O2 -Wall -DLUA_ANSI    -c -o lua.o lua.c
yes
checking whether gcc accepts -g... cc -O2 -Wall -DLUA_ANSI    -c -o luac.o luac.c
cc -O2 -Wall -DLUA_ANSI    -c -o print.o print.c
yes
checking for gcc option to accept ISO C89... none needed
checking how to run the C preprocessor... gcc -E
ar rcu liblua.a lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o strbuf.o lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o # DLL needs all object files
ranlib liblua.a
cc -o lua  lua.o liblua.a -lm 
cc -o luac  luac.o print.o liblua.a -lm 
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for ANSI C header files... liblua.a(loslib.o): In function `os_tmpname':
loslib.c:(.text+0x29c): warning: the use of `tmpnam' is dangerous, better use `mkstemp'
make[3]: Leaving directory `/home/constantius/redis/deps/lua/src'
yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking size of void *... 8
checking size of int... 4
checking size of long... 8
checking size of intmax_t... 8
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking whether __asm__ syntax is compilable... yes
checking whether __attribute__ syntax is compilable... yes
checking whether compiler supports -fvisibility=hidden... yes
checking whether compiler supports -Werror... yes
checking whether tls_model attribute is compilable... no
checking for a BSD-compatible install... /usr/bin/install -c
checking for ranlib... ranlib
checking for ar... /usr/bin/ar
checking for ld... /usr/bin/ld
checking for autoconf... /usr/bin/autoconf
checking for memalign... yes
checking for valloc... yes
checking configured backtracing method... N/A
checking for sbrk... yes
checking whether utrace(2) is compilable... no
checking whether valgrind is compilable... no
checking STATIC_PAGE_SHIFT... 12
checking pthread.h usability... yes
checking pthread.h presence... yes
checking for pthread.h... yes
checking for pthread_create in -lpthread... yes
checking for _malloc_thread_cleanup... no
checking for _pthread_mutex_init_calloc_cb... no
checking for TLS... yes
checking whether a program using ffsl is compilable... yes
checking whether atomic(9) is compilable... no
checking whether Darwin OSAtomic*() is compilable... no
checking whether to force 32-bit __sync_{add,sub}_and_fetch()... no
checking whether to force 64-bit __sync_{add,sub}_and_fetch()... no
checking whether Darwin OSSpin*() is compilable... no
checking for stdbool.h that conforms to C99... yes
checking for _Bool... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating doc/html.xsl
config.status: creating doc/manpages.xsl
config.status: creating doc/jemalloc.xml
config.status: creating include/jemalloc/jemalloc.h
config.status: creating include/jemalloc/internal/jemalloc_internal.h
config.status: creating test/jemalloc_test.h
config.status: creating config.stamp
config.status: creating bin/jemalloc.sh
config.status: creating include/jemalloc/jemalloc_defs.h
config.status: executing include/jemalloc/internal/size_classes.h commands
===============================================================================
jemalloc version   : 3.2.0-0-g87499f6748ebe4817571e817e9f680ccb5bf54a9
library revision   : 1

CC                 : gcc
CPPFLAGS           :  -D_GNU_SOURCE -D_REENTRANT
CFLAGS             : -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -fvisibility=hidden
LDFLAGS            : 
LIBS               :  -lm -lpthread
RPATH_EXTRA        : 

XSLTPROC           : 
XSLROOT            : 

PREFIX             : /usr/local
BINDIR             : /usr/local/bin
INCLUDEDIR         : /usr/local/include
LIBDIR             : /usr/local/lib
DATADIR            : /usr/local/share
MANDIR             : /usr/local/share/man

srcroot            : 
abs_srcroot        : /home/constantius/redis/deps/jemalloc/
objroot            : 
abs_objroot        : /home/constantius/redis/deps/jemalloc/

JEMALLOC_PREFIX    : je_
JEMALLOC_PRIVATE_NAMESPACE
                   : 
install_suffix     : 
autogen            : 0
experimental       : 1
cc-silence         : 1
debug              : 0
stats              : 1
prof               : 0
prof-libunwind     : 0
prof-libgcc        : 0
prof-gcc           : 0
tcache             : 1
fill               : 1
utrace             : 0
valgrind           : 0
xmalloc            : 0
mremap             : 0
munmap             : 0
dss                : 0
lazy_lock          : 0
tls                : 1
===============================================================================
cd jemalloc && make CFLAGS="-std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops " LDFLAGS="" lib/libjemalloc.a
make[3]: Entering directory `/home/constantius/redis/deps/jemalloc'
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/jemalloc.o src/jemalloc.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/arena.o src/arena.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/atomic.o src/atomic.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/base.o src/base.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/bitmap.o src/bitmap.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/chunk.o src/chunk.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/chunk_dss.o src/chunk_dss.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/chunk_mmap.o src/chunk_mmap.c
src/jemalloc.c: In function ‘je_realloc’:
src/jemalloc.c:1082:9: warning: variable ‘old_rzsize’ set but not used [-Wunused-but-set-variable]
  size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
         ^
src/jemalloc.c: In function ‘je_free’:
src/jemalloc.c:1230:10: warning: variable ‘rzsize’ set but not used [-Wunused-but-set-variable]
   size_t rzsize JEMALLOC_CC_SILENCE_INIT(0);
          ^
src/jemalloc.c: In function ‘je_rallocm’:
src/jemalloc.c:1477:9: warning: variable ‘old_rzsize’ set but not used [-Wunused-but-set-variable]
  size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
         ^
src/jemalloc.c: In function ‘je_dallocm’:
src/jemalloc.c:1622:9: warning: variable ‘rzsize’ set but not used [-Wunused-but-set-variable]
  size_t rzsize JEMALLOC_CC_SILENCE_INIT(0);
         ^
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/ckh.o src/ckh.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/ctl.o src/ctl.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/extent.o src/extent.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/hash.o src/hash.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/huge.o src/huge.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/mb.o src/mb.c
src/ctl.c: In function ‘epoch_ctl’:
src/ctl.c:1112:11: warning: variable ‘newval’ set but not used [-Wunused-but-set-variable]
  uint64_t newval;
           ^
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/mutex.o src/mutex.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/prof.o src/prof.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/quarantine.o src/quarantine.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/rtree.o src/rtree.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/stats.o src/stats.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/tcache.o src/tcache.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/util.o src/util.c
gcc -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops  -c -D_GNU_SOURCE -D_REENTRANT -Iinclude -Iinclude -o src/tsd.o src/tsd.c
ar crus lib/libjemalloc.a src/jemalloc.o src/arena.o src/atomic.o src/base.o src/bitmap.o src/chunk.o src/chunk_dss.o src/chunk_mmap.o src/ckh.o src/ctl.o src/extent.o src/hash.o src/huge.o src/mb.o src/mutex.o src/prof.o src/quarantine.o src/rtree.o src/stats.o src/tcache.o src/util.o src/tsd.o
make[3]: Leaving directory `/home/constantius/redis/deps/jemalloc'
make[2]: Leaving directory `/home/constantius/redis/deps'
    CC adlist.o
    CC ae.o
    CC anet.o
    CC dict.o
    CC redis.o
    CC sds.o
    CC zmalloc.o
    CC lzf_c.o
    CC lzf_d.o
    CC pqsort.o
    CC zipmap.o
    CC sha1.o
    CC ziplist.o
    CC release.o
    CC networking.o
    CC util.o
    CC object.o
    CC db.o
    CC replication.o
    CC rdb.o
    CC t_string.o
    CC t_list.o
    CC t_set.o
    CC t_zset.o
    CC t_hash.o
    CC config.o
    CC aof.o
    CC pubsub.o
    CC multi.o
    CC debug.o
    CC sort.o
    CC intset.o
    CC syncio.o
    CC migrate.o
    CC endianconv.o
    CC slowlog.o
    CC scripting.o
    CC bio.o
    CC rio.o
    CC rand.o
    CC memtest.o
    CC crc64.o
    CC bitops.o
    CC sentinel.o
    CC notify.o
    CC setproctitle.o
    CC redis-cli.o
    CC redis-benchmark.o
    CC redis-check-dump.o
    CC redis-check-aof.o
    LINK redis-check-aof
    LINK redis-check-dump
    LINK redis-benchmark
    LINK redis-cli
    LINK redis-server
    INSTALL redis-sentinel

Hint: To run 'make test' is a good idea ;)


make[1]: Leaving directory `/home/constantius/redis/src'


What useful information can you make out of this sheet of text? None, I bet. I wouldn't even read it. By the way, did you notice several warnings from the compiler? No? Well, that's the point.

Having lengthy verbose output makes you ignore most of it. If you're extremely wary most of the time, you can try to force yourself to parse the ever-increasing log for useful information — such as aforementioned compiler warnings. But most developers won't do that. That's irrational thing to do.

That last part of make log is slightly better:
CC adlist.o
It's understandable and gives just the most general information — more like just progress report. Which is still way below the high standard of conciseness set by Unix. Consider this:
➜  redis git:(464fef9) make -j8

src/jemalloc.c: In function ‘je_realloc’:
src/jemalloc.c:1082:9: warning: variable ‘old_rzsize’ set but not used [-Wunused-but-set-variable]
  size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
         ^
src/jemalloc.c: In function ‘je_free’:
src/jemalloc.c:1230:10: warning: variable ‘rzsize’ set but not used [-Wunused-but-set-variable]
   size_t rzsize JEMALLOC_CC_SILENCE_INIT(0);
          ^
src/jemalloc.c: In function ‘je_rallocm’:
src/jemalloc.c:1477:9: warning: variable ‘old_rzsize’ set but not used [-Wunused-but-set-variable]
  size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
         ^
src/jemalloc.c: In function ‘je_dallocm’:
src/jemalloc.c:1622:9: warning: variable ‘rzsize’ set but not used [-Wunused-but-set-variable]
  size_t rzsize JEMALLOC_CC_SILENCE_INIT(0);
         ^
src/ctl.c: In function ‘epoch_ctl’:
src/ctl.c:1112:11: warning: variable ‘newval’ set but not used [-Wunused-but-set-variable]
  uint64_t newval;
           ^

The warnings are right there now. Up front and clear.

What does it cost? Just suppress all the output to stdout. Or, if you're writing your own tool: don't output anything except your immediate result to standard output until asked explicitly.

To be honest, I myself am guilty of verbose output of build system. I've implemented CC file type of output and that's still too much even for our not-so-big project. I guess it's easy to get attached to feeling of importance given by lots of output of your system.

Okay, let's not stop on my favorite kind of software, which is build systems :)

Recently I've come across a test, which output something along these lines:
...
case 12: passed
case 13: skipped
case 14: passed
...
You know what's "skipped" here? It doesn't mean the test can't run on current configuration or was disabled manually. Here it means the code for this particular case was removed to distinct testing executable and therefore the case is "reserved for future use". Could you guess it?

What I'm getting at is this: designing proper information, error, and debugging messages is hard. To be both helpful and short, the output should be carefully crafted. And until this is the case, the program should be silent — to not mislead anyone.

It's better to have a proper error code of the tool and no error message besides vague "Error", than elaborate screenful of explanation what went wrong. Having the error code one can look it up in manual or in the source. But having some complex (or even worse — outdated) explanation of error may induce a misunderstanding and false sense of trust to help message. Seeing some message developer won't even consider investigating the error further — the program already tells what's wrong! Only it does it the wrong way.

We're coming to generalization of the rule: one shouldn't supply the client with information until client wants to have it. When the user wants to know, he's already primed to be thinking in right context. When the program just spits all the information it has, user is tempted to blindly trust it.

The reasoning applies not only to command-line tools. Unnecessary modal message boxes, verbose dialogues, and blinking buttons are all species of the same kind.

So, just to summarize and repeat it once again: the principle of silence is really just another expression of law of encapsulation.

Black boxes are silent. And silence is golden.

comments powered by Disqus