Hacker News .hnnew | past | comments | ask | show | jobs | submitlogin
On Using Debuggers (plope.com)
51 points by hardik988 on Jan 20, 2011 | hide | past | favorite | 61 comments


> "It saves me over and over again when dealing with code I don't understand."

This is the key statement of the original post.

If you don't understand the code, then you need to sit down and read it until you do.

Using a debugger can lure you into quick, localized fixes to make small problems disappear. If you just need to make a minor fix to an external library that you'll never look at again, this is probably OK. But if you own the code, you're in a rut.

Constant use of a debugger is a crutch that prevents you from deeply understanding a problem or discovering core issues.

In the long run, a debugger slows you down. Don't step through code to verify fixes; write automated tests. Don't set breakpoints to verify that a value is what you expect; write assertions to confirm that they will always be so. Don't step through new code to verify it is correct; write a unit test first or compose your new code from smaller, proven pieces.

When confronted with a bug, sometimes the best thing to do is just stare at the ceiling and think about what might be causing it -- quite often you'll find the right answer.

Some bugs -- the really hard, nasty ones -- may never be revealed by stepping with a debugger. Performance problems aren't shown by a debugger. Only understanding your system at a high level can inspire you to make fundamental changes in your code. A debugger blinds you to these.


I agree with what you're saying here. However, when things start getting seriously weird, an assembly language debugger can be invaluable.

For example, if your C compiler is generating bad code, or if you've violated some low-level assumption, it's hard to figure out what's going on by inspecting the source code. Sooner or later, the high-level abstractions break, and there's nothing you can do except single-step through the code that's going wrong.

I once found a bug in MSVC++ while compiling Quake 2. The compiler was performing a tail-call optimization, but it hadn't noticed that Carmack had stored a pointer to a local variable in a global variable. The stack frame was overwritten while the variable was still live, causing weird stuff to go wrong. This is not the kind of bug that you can quickly find by staring at C code. :-)


These are great edge cases, but certainly not the common case. I think, in general, people jump to a debugger well before they're ready to actually understand what the debugger is telling them. This turns into a random walk through the source and ends up being a pretty big waste of time, compared to actually reading and understanding what's going on.

That being said, I'm planning on using gdb this afternoon to debug an x86 (16-bit) bootloader I wrote 4 years ago. It was part of a class project, and the prof ended up using it as example code. I think there are subtle bugs in it though...


While i don't often use debuggers, i find your argumentation to be false, at least for me, for one very simple reason :

A debugger can be a very fine tool for understanding how code you don't know works. Sometimes better than just reading it. Always better than litering the code with print statements.


I don't mean to imply that a debugger is not an invaluable tool. But it's just one tool of many, and it's an addictive one.

I once had to work for a few years with a development environment that didn't have a debugger. At first, this seemed like it would be impossible.

But I learned ways to work without one, and now I find that I prefer them. Only rarely do I actually require a debugger. I do still pull a debugger out occasionally for particular needs, but by and large I find that I work faster with simpler tools -- or by just stopping to think for a moment before I reach for the keyboard.


What I tell others: I'm so good that I don't need a debugger.

The truth: Print statements work so well that they dramatically decrease the need for a debugger. Then when I actually do need a debugger, I have used it so infrequently that I forgot the commands, so I just use more Print statements.


Not using a debugger is like giving up a knife and using a spoon to eat your steak. Yeah, you can do it, but why would you? Debuggers are invaluable when you encounter difficult bugs, especially ones related to threading issues. Being able to freeze/run threads at will comes in handy. Setting hardware breakpoints to see when particular memory locations are changing is also useful. Maybe you're just not using the debugger to its full potential?


Proper logging > debugger.

There is absolutely no point single stepping through code, setting up breakpoints, inspecting variables etc, when you could instead just insert an extra log statement to see what is going wrong.

If your logging is good, often solving a bug is just a case of looking in a production log and seeing where things went wrong.

The other fact is that the majority of bugs I've seen in my own code have been caused when under load, and it's not feasible to run a debugger on a production system.

I've tried debuggers a fair amount, and they end up being completely useless to me.


"Proper logging > debugger."

I assume that statement refers to the amount of noise in question?

Sure, print statements are my normal tool, but often I'm working on transforming one data structure into another (compiler-style) and I'm encountering an error "somewhere" down in the middle. After the first few megabytes of logging output get generated, you start to really lose the thread of what's going on.

It depends on what you're doing. If you're a CRUD programmer, yeah, I can see how they'd be useless; that's a good deal of my job too and I don't use them then. But when I'm actually doing something interesting and twisty I often need them, if only to set some conditional breakpoints somewhere so I can get into the middle of a complicated process instead of trying to reconstruct the state later.

Also they are great for learning libraries by stepping through them.


Logging changes the timing of code making it nearly useless for debugging problems of a concurrent nature. It also falls down when systems cannot afford the overhead of printing all trace data (see: video games), or when embedded platforms have no reasonable TTY to send log data to.

A debugger is just a tool and to dismiss a tool as being pointless for all people is ridiculous. Being familiar with all the tools at your disposal as a software developer is never a bad thing.


A great reason why you shouldn't use concurrency...


Not all developers work on the same sorts of problems in the same way with the same type of time pressure.

Debugging helps you work with code you don't understand. So, the longer you work with the same code the less useful it becomes. However, plenty of coders move from project to project patch up a few problems and then move on.


I would say that logging is fine sometimes, perhaps most of the times but being able to hotswap new code, do a deep inspection of an object (and perhaps some further new evaluation of code that you hadn't previously thought of) comes in very very handy.


Debugging is just another tool. It's not the most valuable tool, but it's also not the least valuable. At work we rely heavily on logging and trace statements. There isn't even a functioning debugger for our UI language implementation.

I'd say not having a debugger is more like programming on paper. When you have a problem, you might end up thinking about it a little more because you can't rely on that one other tool.


"Not using a debugger is like giving up a knife and using a spoon to eat your steak."

I think it's more like never being taught how to use a knife, and thus eating the steak with your bare-hands, as you've always done. Hands work fine, why learn something new? I have no formal training in programming and it never really occurred to me to use a debugger. I still don't entirely understand what they do for you that you can't do by adding print/trace statements. That said I mostly use scripting languages and don't need to read too much code written by other people...


The debugger, after your brain, is probably the most powerful tool in your arsenal.

They allow you to evaluate state and often see things you'd miss otherwise.

Plus they can do what prints can do, but even easier. I can use tracepoints to print anything at any point in code, including the callstack. The nice thing though is I can turn the tracepoints on and off while never pausing/breaking the code.

Additionally I can attach a debugger to a running process.

Plus with more modern features, like historical debugging), I can go back to predefined instances of time (like a past exception that was thrown) and examine the callstack then.

And the other thing you can do interactively is (a) change the value of variables, (b) change the code, and (c) change the instruction pointer. I regularly do (a) & (c) -- (b) less so. It's a huge time saver when you step over function foo() and realize, hey it didn't set ABC. So then just set the IP back to foo(), but this time step into it.

When I would consult I would often find and fix bugs in a day or two with oode that I'd only seen for the first time, while others had been looking for weeks in code they had helped build. I'd look at their debugging techniques and it was obivous that they were often just not at all efficient.


Cutlery and meat have nothing to do with debugging.


> What I tell others: I'm so good that I don't need a debugger.

That sounds better than what I tell others, that I'm not smart enough to use a debugger.

Debuggers let me get away with code that is on the edge of my understanding at the time that I'm writing it. The problem is that I'm not nearly as smart when I have to revisit that same code later.

In addition, debugging with print requires more understanding, so it's more likely to be correct than debugging with a debugger.

That said, I really liked my time in visual studio.


"In addition, debugging with print requires more understanding, so it's more likely to be correct than debugging with a debugger."

I don't believe this is true. I suspect the way most people use print statements for debugging goes something like: "I think the problem is somewhere in this function, let's stick a print statement here. Oops, that doesn't seem to be it, let's try one here. Oops, that's not it either, let's try printing out a different variable." and so on.

That's remarkably similar to using a debugger only a lot less efficient, because you have to go back through the whole edit-compile-run cycle each time (or maybe edit-deploy-run if you're working on a web app using a scripting language).


> I don't believe this is true. I suspect the way most people use print statements for debugging goes something like: "I think the problem is somewhere in this function, let's stick a print statement here. Oops, that doesn't seem to be it, let's try one here. Oops, that's not it either, let's try printing out a different variable." and so on.

What is the basis for your suspicion? I ask because the folks who I see using print as their first-choice for debugging don't work that way. The overhead associated with changes (as opposed to restarting) gives them the opportunity to think (or grab a new cup of tea).

That said, that "poke around randomly" approach does seem to be "not rare" for folks who are "debugger first", as you yourself say.

To be fair, the folks I know who are most effective with a debugger also put in some time on understanding before they fire it up.


What I tell people who tell me this: You're probably making a simple or sequential app.

Debugger is more powerful, it can do everything print can, and more. Sure if it's a 5 minute walk to the market, then by all means, walk.


Hahaha! This is exactly what I wanted to say!


You should also be careful about taking your opinions on debugger usage from compiler, virtual machine runtime, and operating system implementers. Debuggers, particularly gdb, are seldom useful for more than crash dump analysis when you're looking at situations such as: - Bad optimized code being generated - Crashed trying to do something like execute an integer (happens with GC race conditions) - Deadlock that only happens on maximum number of available cores when no debugger is attached

I've had to deal with all of those specific issues in the last couple of months on our compiler+runtime, and there's sadly not a lot of tools in the free space to help with situations like that.


Queue 1000 comments from people gloating about how they too are "too good for debuggers". I work with a number of people who don't use a debugger, primarily because they never took the time learn how to use one. They often spend hours and hours inserting print statements, recompiling, redeploying, etc. for problems that can be solved in 5 minutes by using a debugger. Print statements are fine if you're debugging your "vending machine" project for CS101, but they don't work well for large, complex projects where you only work on a subset of the codebase. Don't let me dissuade you, though -- you guys keep using your print statements, and I'll be the superstar getting the same work done in a fraction of the time.


That's why you use proper logging :/

If you're making something of a reasonable size, you should be logging tons of stuff.

If you've made sure your logging is solid, often fixing a bug is just a case of looking in the log to see what went wrong.


I agree that proper logging can replace the need for a debugger in many instances, but so can proper testing, talented devs, unlimited time, unlimited budget, etc.

Under ideal circumstances (i.e. not real world), no one would ever need to fire up a debugger, but (most of us) don't work in those conditions.

Can you create world-class software using only print statements to debug? Sure. Is that an efficient use of your time? Probably not.

The bottom line is debuggers are just another tool in the development aresenal, and dismissing them merely because you can accomplish the same task in another way is asinine.


I don't dismiss them because of that, I dismiss them because I can do the same job quicker by thinking, knowing the code, being able to read and understand any code I didn't write, and inserting a couple of print statements.


A lot depends on how good your debugger is. If you're coding C# in Visual Studio or Java in Eclipse, the debugger is dead easy to use and works well for solving problems.

On the other hand, the only thing I use gdb for is cheating at nethack (+99 blessed Unicorn horn anyone?) and looking for buffer overflows. There are a few PHP debuggers out there but I've yet to get one to really work right.

My main trouble with printf-debugging in all it's forms is that you end up making changes to code that don't always go away. Even if you do it right 99% of the time, you'll insert 1000's of printf's over the course of a month and sooner or later one of those will end up visible to end users, will fill up your logs with junk messages, or otherwise gunk up the works.

Of course, there's something to say with fixing bugs by looking at the code ~thinking~. Yesterday I was tracking down a bug involving an area of code that I didn't trust, and ended up refactoring it, converting some properties in an interface into readonly methods of an abstract base class so I knew for a fact that certain variables wouldn't change after initialization.

This work convinced me that the code was wack, but when it was done, I knew the problem involved initialization of the objects. For a second I thought about using the debugger, but finding the specific case that was causing trouble would have been tough.

At that point I decided to trace the codepath involved and immediately saw the cause of the problem and understood the root cause (certain initialization code was duplicated in two places, which is just asking for things to break.)

I could have fixed the bug quicker by just following the codepath, but spending some time cleaning up code I was scared of will pay dividends in the future... And it's a good example of the tradeoffs you face when you fix bugs.

That said, I particularly find printf-debugging useful when something strange and difficult-to-reproduce is going on, particularly in a production system.


> On the other hand, the only thing I use gdb for is cheating at nethack (+99 blessed Unicorn horn anyone?) [...]

Might as well use wizard mode, and wish for that stuff.

I prefer playing on public servers, they take away that temptation, and give you more bragging rights.


[Full disclosure: I make heavy use of both debuggers, and printf debugging, in my work.]

A debugger gives you a detailed picture of the state of your program at one moment in time, or (when stepping) over a short period of time. Other tools -- logging, monitoring, etc. -- provide a different slice through your program state, making it much easier to observe the flow over time, but relatively little detail about any given instant. Each approach has its uses.

Logging has a number of properties that make it relatively more useful for experienced programmers, mature codebases, and/or "infrastructure" systems (compilers, operating systems, libraries) that are used far from the developer's workstation. That might explain its popularity among the group interviewed in this book. For instance:

1. Programmability. The nature of logging -- both generation and analysis -- lends itself to building up a library of utilities. An experienced programmer develops a toolkit -- part code, part knowledge -- that makes logging more and more useful to him/her over time. There's also a learning curve for debugging techniques, of course, but it flattens out sooner. I've been programming for 35 years, and my logging techniques are better than they were even 5 years ago, but my productivity in the debugger plateaued decades ago.

2. Accumulation. If you work on a single body of code for a long time, you can continually improve the usefulness of its logging. Eventually you reach the point where many bugs are quickly obvious from the log, because you'd previously invested a lot of effort in logging exactly the data you typically need, in the most convenient format. Printf debugging can take a long time if you're starting from scratch (the program contained no relevant logging to start with), but in a mature codebase where attention has been paid to logging, the story can be very different.

3. Universality. Logging tends to work in a wide variety of environments, from manual testing on the developer's workstation, to production servers, to installations on a customer's computer. If you spend your life tracking down hard-to-reproduce bugs reported by other people, logging is essential.

4. Repetitiveness. Tracking down problems in a debugger tends to involve a lot of boring, repetitive actions -- "aha, this is null because that other flag had been set, let me restart and walk through the whole thing again to see when that happens". It's still sometimes the best tool for a job, but it has a time-sink flavor that probably pushes some people away.


This. With hard crash/memory bugs I lean towards the debugger, but viewing the flow over time is invaluable for more subtle logic bugs.


Here's a wild assertion about why some programmers - some good programmers - prefer printfs: good programmers prefer to solve problems only once, rather than repeatedly.

When you have a question you want to answer about the state of a program, a debugger gives you powerful tools to answer your questions right now, but doesn't leave any permanent record of your investigation. Each time you subsequently want the answer to that same question you have to run the program, hit that breakpoint again, and cast whatever incantations are necessary to reveal the relevant program state.

If you used printfs instead, you would probably have had to iterate a few times to get the answer the first time; first you print one thing, then another, moving your prints around until they tell you what you want to know. When you're done, though, you've probably left some log output hanging around. So after you're done investigating, say, how the data access layer works, you have something to cross reference while you're poking around the user interface code.

I think this is a more useful technique for unfamiliar source. Comprehending a large hunk of source typically requires integrating your understanding of several sections of code ranging widely across the codebase.

Anyway - a good debugger is a wonderful thing. I can't stand up straight sometimes for leaning on them, though.


When I think debugger, I think gdb, which is fine to a point. But after a while, its interactivity becomes a liability. I don't want to have to set the same breakpoints and watches after a recompile, every time.

The integrated debuggers in IDE's are much better, particularly the Java ones. The C/C++ ones have the problem that they will work fine on code compiled with debug symbols, but on release code they're awful.

And when it comes down to it, the really hard to fix problems are the ones that only show up in the optimized release code.


You can rebuild your program while you're running gdb on it. Next time you restart the program you're debugging, it will pick up the rebuilt version and all your breakpoints and watches will still be intact. There's even a built-in gdb command for running make, for precisely this reason.


I find knowing what the stack is at the point where things go wrong incredibly useful. Having an IDE that allows me to work my way back up the stack and see what direction my code was called from that meant that I ended up with an invalid state is terribly handy.

I also sometimes find it useful when calling code I don't have decent documentation for. I can examine the various properties of the object I'm trying to work with, and see which ones contain useful information.


What I don't read much about, but depend on, is using a debugger on new code when there aren't obvious bugs in it. Just walking through it, while viewing variables, gives a wonderful sanity check to verify that code is doing what I think it is doing, and this catches a surprising amount of non-obvious bugs, and lines of code become easier to reason about precisely sometimes when you have visibility of the specific state of the program at that time. This is not a substitute for unit testing obviously, but it can catch problems that wouldn't be obvious or easy to unit test for.

For me, this is also a wonderful way to get accustomed to someone else's code, seeing exactly what it does first before spending the brain cycles upfront to reason about exactly what is going on.


The print statements to debug was a constant in "Coders at Work" too. Also in that book I found interesting that everyone interviewed was asked what the most difficult thing they had to debug was, and almost all of them said something to do with concurrency.


That's not surprising though, if there's one thing I've had drummed into me throughout my education and career, it's that concurrency is /hard/. Particularly since it requires thinking about things in a completely different way to a lot of single-threaded programming.


Yes, and its because programming languages actually provide almost nothing to help out here.

E.g. methods cannot be declared 'non-reentrant'. Data cannot be declared 'atomic'. Yes, you can hack together solutions using library methods etc. but the language doesn't help much.

We wrote an 'operating environment' for a major computer manufacturer once - a storage device they eventually cancelled (hardware was problematical). Programmers wrote objects that were each invoked single-threaded, passed workitem messages between services instead of directly calling. The environment managed message queues, handled all the threading and reentrancy invisibly.

The programming team loved it. They could write restartable, failoverable modules in this model without every dealing for an instant with concurrency explicitely.


It's also completely avoidable.

Concurrency is only hard because it's a stupid thing to do - to give a bunch of instructions to the CPU and tell it to execute them in a random order.


Why not, if you compiler proves that it's safe?

I don't even care in which order my single-threaded code gets executed (if it's in the right language).


I almost never use a debugger as well (used to rely on it when I was a beginner though). Even when I'm looking at someone else's code, my time is generally spent more productively analyzing it with pencil and paper than running a debugger.

As some one else mentioned, I typically only use gdb for crash dump analysis. The one tool I do use a lot though is Valgrind (to check for memory leaks).


I had this conversation about a decade ago at http://www.perlmonks.org/?node_id=48495. Unfortunately most of the links in that discussion have gone dead. Here are fixes to the most important ones.

The Kernel Traffic summary: http://kt.earth.li/kernel-traffic/kt20001002_87.html (sections 1 and 4 are relevant).

Linus' "I'm a bastard": http://lkml.indiana.edu/hypermail/linux/kernel/0009.0/1148.h...

Richard J Moore discussing IBM's experience: http://lkml.indiana.edu/hypermail/linux/kernel/0009.1/1307.h...


For C/C++ the debugger is an invaluable tool.

For large projects (especially large projects that don't have the files structured well) you can end up with very high compile and link times (> 2 minutes). Using a debugger in this case is much faster than incrementally adding print statements to the code.

Being able to see a callstack is also incredibly valuable on complex projects. The value of printing things out drops dramatically once you have multiple entry points into the same codepath.

Also, if you always run your program through the debugger, when you crash you can immediately diagnose and fix the problem without having to reproduce the crash. This is useful for hard to reproduce bugs.

Besides, it's not like this is an 'either or' type thing. You can use a combination of reading the code, debugging, and printing to track down problems.


I understand this - debuggers are useful (especially when going through code you haven't written), but if you're writing an entire program yourself, you understand how it works completely, so a debugger is much less necessary (not unnecessary).

I haven't fired up a debugger for a long time, partly because I'm in to defensive programming big time. If you have something as simple as assert(ptr != NULL) in a function, if it fails, you get hit with an assert failed message, the file, line, function and condition. Most of the time this results in me thinking, "oh, duh" and immediately correcting the problem, sans debugger.


Using a debugger on pre-production code is a waste of an opportunity to create good tests.

A debugger can be useful when debugging production code, especially when the heat is on. This is no time to stand on ceremony and idealism.

For learning new code, particularly frameworks ime, a step debugger is incredibly valuable. But you are not really debugging in that case.

A debugger is a non essential tool these days. But if the kernel boys said they found it useful, then I'd not argue.


I used debuggers long ago, but I've found that I work just as well, if not better, without them. I have to actually understand the code, not just get to a certain point and see what the variables are.

In the end, it means I know that section of code inside and out and can rewrite and/or refactor it as needed to solve the problem. It usually results in cleaner, more efficient code.


Its a tool, like a yellow hammer; useful when you need it.

Since I write time-dependent C++ code I must test/debug the 'release' build. The debuggers universally screw up almost everything related to dynamic symbols (locals, parameters), so I have to read the assembler anyway to figure out what's where.

Naturally this makes it much quicker to use prints (logs actually) than struggle in the debugger.


Wouldn't the print statements have the possibility of screwing up the optimized code (changing register allocations, for example)? Or screwing up the timing? (I realize a debugger would definitely screw up the timing)


So I don't actually use prints; I make log entries in a system service with an enormous buffer. It takes negligible time, certainly compared with a debugger breakpoint!

Unless we suppose a compiler bug, reallocating registers around a print (ok, log) is not a problem. It's still an order of magnitude faster than the 'debug' build.


I agree with what several have already pointed out here, that a key point is that the famous programmers interviewed already understood their code and hence using a debugger to better understand it is not particularly useful.

I've been struck over the years at how one of key benefits of the Smalltalk environment -- being able to explore/change things in a program while it's running -- is not more widely regarded in the field. I occurs to me now that the interviewed programmers might have already gotten much of the cognitive benefit of such exploration at the beginning of a project. For them, print statements and listings are a way of confirming details of what they expect to be happening as the code runs. For un-godlike programmers (and maintenance work), debugging is complicated by the lack of deep understanding of how the code is supposed to work, hence the need for more exploration time and debuggers.


I used gdb plenty in C/C++ but that was mostly to debug mysterious errors when exceeding array bounds. Back then continously running server software that crashed would essentially move away its core file and restart, then just run gdb on it and mail be the output of a stack trace and "info locals".

In Python that's sufficient to catch most logic errors -- a full stack trace with values of all locals and member variables is enough to diagnose 90% of errors. When the error does not lead to an exception but undesired behaviour print debugging is easiest -- I have the software running so it restarts on source code changes and the restart takes a second and does not lose any state as it's a web app.

I could see myself using pdb if the Python software was a more complex server that took longer to restart and had some complex state that'd be lost on restarting.


I'm surprised to read more people arguing, "print statements and/or understanding the code I'm working on is more valuable than a debugger" rather than straightforward, "my platform/language/environment doesn't have a good debugger--print statements are simply easier." pdb is pretty good but wedging it into my FastCGI processes running as another user is a PITA (albeit one I could fix just once). Clojure has CDT but that's even kind of PITA-y since I have to go start up another JVM and get them to talk to each other. It's much easier just to type "print x, y, z" and reload my code.

If I was in VS.Net or Eclipse and not Emacs all day, and/or if I was writing something more "straightforward" like a desktop application, I bet I'd be much more inclined to use a debugger.

(Edit for word choice.)


I couldn't imagine coding C# without a debugger (Visual Studio).

I couldn't imagine coding Ruby or PHP with a debugger.

Maybe I've just not been incentivised enough to learn how to properly debug Ruby/PHP though.


FTA: "It saves me over and over again when dealing with code I don't understand. Not having it would be terrible."

Perhaps that is the problem. What are you doing working with, using, or writing code that you don't understand

To me this is a bit like the folks who randomly change their loop conditions until they get the desired behavior. Sure it works correctly now, but they have no idea why their changes made it that way.


I think you're missing the point of the quoted statement.

A debugger is a great tool to help you understand unfamiliar code. Drop me in to a breakpoint in somebody else's codebase, and chances are I'll quickly be able to figure out what's going on.

Assuming you didn't write all the code in your organization, debugging is pretty much your most valuable skill. Be it looking at a stack trace and digging in by hand, or using the tools available to you. Your worth revolves around being able to immediately dig in to an unfamiliar system, find our what's wrong, and fix it.

Debuggers help you do that.


My work is mostly in distributed real-time systems. All of the really hard debugging problems (e.g., race conditions) occur in situations where you can't pause one of the components with a debugger or you won't be able to repeat the problem. In those cases, logging events and studying them right after repeating the problem is your only recourse.


In 98% of my time i never use them (Java/IDEA IDE).

I use profiler a lot lately though (Java Visual VM).


A debugger loses much of its value in a functional language. Debugging Haskell or functional Scala code with a visual debugger is almost counterproductive.


I can't believe that someone would use a language that didn't have an IDE/interactive debugger. It's like going backwards in time to the early 80's. :-)


> ... Bertrand Russell ...

wat




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: