Practice, lots of practice. Doesn't really matter what kind of program you write as long as you write code. Can be assignments from your university, can be online puzzles like advent of code, can be a program that sends you reminders to kiss your wife, can be the 984397594th minecraft clone, or whatever else you want. Basically if you have a problem that can be solved by coding, do that.
Access to a programming environment with good discoverability, and reasonable examples of working code that I could tweak. Back when I learned, examples abounded in print material, and print documentation tended towards complete. (Especially Turbo Pascal's manual)
This made it possible to experiment, and get started. Since then it's a matter of repetition, deliberate practice, and raw experience over time.
Everything has scaled towards good thanks to Moore's law, the Internet, and GIT. You can't overstate how much better git makes things.
1. Having a passion project that made it easy for me to sustain lots of hours of coding as I strove to complete the project.
2. Reading widely in the coding literature, from personal blogs to engineering blogs to O'Reilly (etc.) books, to "philosophy" stuff like Bentley's Programming Pearls
3. Regular LeetCode. Not grinding exactly, but disciplined practice of solving specific, short challenges.
4. Browsing OOP (other people's programs) on github and such.
I've just been programming a lot since I was about 10 years old. Persist in doing anything for 25 years of your life and odds are you'll get decent at it.
I've never deliberately practiced or anything like that, just built stuff that I wanted to build.
The single most important concept separating a good from a bad programmer is the concept of the invariant. Good code maintains (or anyway restores) invariants, bad code doesn't.
It is very easy to miss this because invariants are often not written down, and you have to discover them.
Related are preconditions and postconditions. Function A establishes postconditions that satisfy the following function B's preconditions.
Your goal maximizing merit of code is to ensure invariants, preconditions, and postconditions may be stated as simply as possible; and keep them satisfied.
We've banned this account for breaking the site guidelines. Please don't create accounts to do that with.
If you don't want to be banned, you're welcome to email hn@ycombinator.com and give us reason to believe that you'll follow the rules in the future. They're here: https://news.ycombinator.com/newsguidelines.html.
What do you mean by invariant? Clearly, the word has special meaning for you, but it is defined as never changing. That definition is not consistent with your emphasis.
An invariant is a predicate expression about the state of computation, typically about values in variables affected by code, but may involve control flow.
An invariant is maintained at entry into and exit from a function. What happens inside the function is usually nobody else's business. You can violate it for a reason, there, if you make sure to restore it before returning.
The role of invariants is much like that of conserved quantities in physics. Things can get very complicated, but energy and momentum conservation mean the amount of chaos is strictly limited.
If they are kept as simple as possible, the code can be more easily understood, and has less scope for bugs.
Are you talking about pure functions? Like math, same input -> same output, no side effects / other state affected. Most programmers would recognize that term instead.
Long day, lots of driving, and then a two hour BJJ class, and then a Spanish study session. My brain is exhausted so this is a bit rambling, sorry:
No, they aren't talking about pure functions. Look into design by contract which brings this idea to the forefront. Invariants are properties that you state about a system that should always hold true over some portion of code. They are related to pre- and postconditions (which are more commonly understood and used, sometimes even explicitly, whereas invariants tend to be less explicitly covered by code).
Pre- and postconditions are properties that hold true before or after, respectively, some segment of code (often defined at function boundaries). Like that an input, even though its type may be int, should always be positive. That's a precondition, you can encode it into some languages, or you can cover it with an assert, or you can just "know" it (far more common). Postconditions are similar, at the end of the computation some properties hold about the returned value or the way that the state of the system has changed.
Some languages (SPARK/Ada, for instance) even let you prove that if some properties are true (basically preconditions become assumptions about the system) then your code will result in the postcondition being true (but not for everything you may want, they're working on pushing the boundaries of what can be proved). And if the system can't prove it, but you believe it to be true, you add other properties about the system or tell it to assume certain things that you may be able to prove by hand (or whatever) but not programatically. Or perhaps the systems proof reveals a problem where the computations will violate one of the conditions. Languages like Idris permit encoding many (most?) of these ideas into the type system which has its own accompanying proof system.
int foo (int n) {
// pre: 1 <= n && n <= 10
// post: 1 <= result && result <= 10
return n * 10;
}
A prover (or anyone with common sense, short example, use your imagination for how this could be more usefully extended) can show that given the assumption of the precondition and the action of the code, the post condition cannot hold. In some languages, without the ability to prove it or even with, you may use asserts (often people disable these for "release" builds because they can be a performance hit, so hope your testing and code reviews are good):
int foo(int n) {
assert(1 <= n && n <= 10); // pre
int result = 10 * n;
assert(1 <= result && result <= 10); // post
return result;
}
Invariants are related to that, they are properties that should hold over some period (perhaps the entire program, perhaps just parts). A common application is the idea of a loop invariant. This is something that is expected to be true about the state of execution of a loop across all iterations. As a trivial example you may have a sorting algorithm (let's not worry about the efficiency of this, brain tired) and you have two indexes (quadratic time worst case). Your outer loop determines the lower bound of the next inner loop:
for i from 1 to size
min = i
for j from i+1 to size
if a[j] < a[min] then min = j // not a good sort, don't do this at home kids
swap a[i] and a[min]
Then you have two invariants: The first portion of the sequence (from 1 to i) is sorted (it may not be the final elements in it, but it is sorted). This holds both before and after the swap. And min is always the index of the smallest value in the subsequence from i to size when we hit the swap. Like I said, this one is trivial, but it's about all my brain can handle right now.
You can also see invariants in the way abstract data types may be used. For instance, to use a simple example, suppose...
PSA: The above DbC wikipedia page contains a link to the paper Applying Design by Contract by Bertrand Meyer which is THE paper to read to learn usage.
If you don't recognize the term as having a specific meaning in programming, it may be worth reading up. This[1] blog post has a great walkthrough of invariants.
This is from formal methods, and while it is one way to specify your code, and contain unexpected behaviour, I wouldn't say it's the main thing in being a good programmer. Managing complexity in general is perhaps the main thing, so that you can do more complex things without getting completely overwhelmed by details.
I think outside of the formal logic space the concept of invariants still are core to programming. Many different strategies such as pure functions, unit tests, type systems, isolation of global state, all key really key around the concept that their are logical truths that always hold for a given program. This is a key way to build large complex programs. it’s impossible to hold every detail of what the code does, so having certain things be known truths goes a long way to enabling one to reason about code.
A lot of reading. I started programming when I was in high school, but procrastinated hard on making project. So I read a lot of books about C, assembler, network, security, operating systems, algorithms. When I started doing projects in university, everything I did made sense conceptually. To this day, when I'm doing project, I can go left and right on the slider [Abstraction----Implementation], very easily, from the architecture of the whole systems, and the gritty details of the particular language and algorithms.
I've been at it for a bit over 20 years and I haven't figured out how to get good at it. I do OK, though, because I learned it's often more important to ship than perfect[0], and rewriting isn't so bad because the second time is usually better than the first.
0: I choose not to work anywhere that sells critical infrastructure, medicine, or anything else where imperfections could jeopardize someone's life.
I have no clue who you are but from reading your answer, I bet your coworkers love working with you and I bet you write nice maintainable code. You are probably better than you think.
18 yrs doing this and I feel exactly the same way. I just love building things on my free time even if i know some of them won't make it out of my dev environment simply because i work too slow. I guess to me, the discovery process (in building something new) is so much fun than say, playing video games.
BTW, I think sometimes is ok to have inline code with a comment of what the extracted function name would have been.
---
Also, by coding using his TPP helped me gain confidence on figuring out complex algorithms. For example, he explains how writing a sort algorithm went from bubble sort to quick sort (I think) by following the TPP.
https://en.wikipedia.org/wiki/Transformation_Priority_Premis...
---
Lastly, as I have a few projects that I can't even run dev on them, because their deps are too outdated, I try to keep deps at a minimum. e.g. I have a custom minifyCSS that minifies ~85% compared to other minifiers, but it's super simple in comparison.
I am not sure that I am good at programming. I am certainly not as good as the HN-type second generation SV kids who started programming at four.
Given that, I do work at a lucrative position doing cutting-edge work.
What made me get programming was- _getting hired_. I was so-so before.
When I got hired, the work was so challenging and the situation was so unforogiving, I had to grow. I had no other way. I learned new stuff, wrote code that inspired me, and was really "impossible" by me a few months ago. Sometimes, a few weeks ago.
What made me and and keep making me better than before is Hacker News and peers.
I want to get as good (from the first paragraph); grow as a programmer, grow as an overall problem-solver and thinker.
I keep learning new paradigms, new applications, gaining new knowledge, and growing as a person.
So the most important thing would be community. Before HN, Reddit. Now, Twitter and Discord, too.
I get to know so many new perspective, so many new resources to follow, books to read, stuff to generally know. I barely have time to breathe.
I also made some genuine, long-term friends through this process.
So, community would be the utmost important things.
I was born in a middle-of-nowhere small town, and community and internet are the ones that made me who I am today.
I am not much, but definitely levels above what I was a decade ago.
Edit: It would be incomplete if I did not mention high-quality teachers teaching for free through MOOCs, YT playlists, etc. And internet pirates, too. Having quick access to any book you might want is very important. (I do spend a lot on books, but piracy is what makes me spend that money so that authors get what they deserve).
I was never good at practicing for practice sake but I also know of the value of "doing the work" to get better at a skill.
So, I become a better programmer when I'm actively working on something. I no longer contribute code on a regular basis at my day job so to stay sharp I've started creating little utility programs for personal use.
I've also started finding hobbies that incorporate some programming, generative art, robotics, etc.
tldr; find ways to incorporate programming into everyday life, practice by building things that are actually useful to you.
- A LOT of practice. One of that parts of becoming a good programmer is learning about potential pitfalls and bad practices; what better way is there of avoiding these than by making these mistakes yourself and learning from it.
- Studying and researching best practices for not just your current language or paradigm, but others as well. If you've got experience with Java, look into other paradigms than OOP such as Haskell / Elm that focus on Functional Programming. This can lead to learning about new ideas of programming in general, which is a good thing as it exposes you to more tools that you can use.
- Making your own things. Find areas in your life that could use some automation, or perhaps use programming as a creative outlet; the more tools you make, the more practice and experience you'll get which leads to the first point I made.
- Cringe at your old code. If it's really that bad, it's likely you've come a long way as a developer, which is good! Reflect from time to time about why you originally wrote code a certain way, and think about how you would approach the same problem now with more experience.
I don't believe I can prioritize these between more or less important things, but this is the set of things that I usually credit:
* Programming competitions. The main utility I got out of this wasn't learning obscure data structures or algorithms (which tend to often not be very relevant), but rather the experience in coming up with corner cases that might break my own code, and how to write and debug code without sitting in front of the computer. I guess in short it's the ability to separate the algorithm from the implementation and track down failures to one or the other.
* Going to school and getting a CS degree. Learning in general is useful, but I believe that the structure of a full CS degree is generally better than picking things up ad-hoc, especially in guiding you towards areas you wouldn't otherwise think to learn about (e.g., I wouldn't have picked up information visualization on my own).
* Seek out and learn as much as you can in general. You never know when randomly learning some topic in some distaff field might come in useful; one time, I happened to discover (while looking up information on ptrace in a debugger-like side-project I was working on at the time) that Linux supported hardware watchpoints via perf... and a few days later, we had to resort to that to track down a bug that wouldn't reproduce in gdb.
* Peruse other projects for how they do stuff. Part of the above, but you'll pick up on "huh, that's an interesting thing to do" as you learn about how they work. If you're curious about how something works, just open up the source code to see how it works. At the very least, doing so frequently will give you a good sense of how to quickly find stuff in large, unfamiliar projects, which is apparently not as common a skill as I take it to be.
I had a really good mentor for many years. He was my manager starting back when I was in high school, and he was always working with us to improve our code. He recommended good books and articles, put in place systems of formal code reviews that emphasized egoless programming, so we could review, learn about, and improve our code without feeling threatened. He helped me think about the code development process and how to be constantly improving it.
He helped me establish many good habits that helped me to grow throughout the years. More than 3 decades later we are still good friends.
i made toy apps and widgets. chrome extensions. took on a bit of client work.
read some books.
stay up all night and hack on projects for a few years while people are sleeping and youll get some where.
never had a sideporject or 'startup' that actually made money, but thye taught me the skills i have now that are paying the bills, and i pretty much have my pick of jobs at this point.
but also suffer from extreme impostor syndrome and try and use that to fill my (perceived) gaps
- A lot of practice, which stemmed from a lot of fooling around and abandoned side-projects. I would deliberately start projects which were way over my head, with no intention of even getting something working, just to see how far I could get before getting bored.
- Learning new languages which had vastly different paradigms than the ones I was used to. Java to Perl to Javascript to PHP to Erlang to Clojure to Go to... Over time you learn patterns from one place you can bring to another, and learn patterns which exist in most places which you'd rather didn't.
- Lots of experience in the actual workforce, building things people (supposedly) wanted. There are aspects of programming which people spin their wheels on which really don't matter in the long run, and conversely there are aspects which go ignored but are very important. Working on real, rubber-to-the-road projects gives you perspective on what actually matters.
- Being a daily archlinux user (I don't use any non-archlinux OS on any machine I own). Yes, it's hard. Yes, it's an incredibly good use of your time to figure out. Once you're comfortable in arch, every other Unix-like OS (ie, most of them that you'll ever probably work with) will feel familiar.
When I needed to make SunOS give me tty key stroke input 1 character at a time, he refused to tell me what he already knew and just pointed me at some "areas to look into". Knowing about kernel tty drivers and terminals and line buffering didn't make me a better programmer, but it didn't hurt. Knowing my way around a variety of different documentation at many different levels of the systems I was working on helped make me a better programmer, and my mentor refusing to just answer my questions was a major factor in that. It also forced me to understand much more of the systems I was working on, rather than just how to use some piece of convenient API.
I'm curious if you've ultimately found your mentor's behavior to be a net positive, or net negative, in your career.
One of the engineers under me often expresses his discontent that I won't give him answers, either. I think he's more than able to get to those answers on his own, and that the value he'll derive from learning how to learn is vastly greater than the value of being able to complete his work faster. On the other hand, he's given me the feedback that the method is excessively socratic at best, and flat-out frustrating at worst.
I think you have to strike a balance. In reality no one's knowledge came entirely from their own work and asking the right questions. Documentation is just one developer telling another how things work. Should we erase all docs?
My first boss out of college (a long time ago) was like this. I'd ask him, "how do I do X?" and he'd reply "man <some-command-useful-to-do-X>". I thought it was annoying, but got it a few years later :) I find myself being the same way these days when it comes to mentoring. Others will help a struggling person, but as long as they aren't crazy frustrated it is destroying their motivation too much, and as long as biz needs aren't being too disrupted, I generally have no desire to help. Why would I deprive them of deeper learning by helping them? ;)
87 comments
[ 3.4 ms ] story [ 145 ms ] threadThis made it possible to experiment, and get started. Since then it's a matter of repetition, deliberate practice, and raw experience over time.
Everything has scaled towards good thanks to Moore's law, the Internet, and GIT. You can't overstate how much better git makes things.
* studying algorithms
* experimenting with different solutions
* learning paradigms, methodologies and best practices
* striving for simplicity
* being curious about how things work
* systems thinking
2. Reading widely in the coding literature, from personal blogs to engineering blogs to O'Reilly (etc.) books, to "philosophy" stuff like Bentley's Programming Pearls
3. Regular LeetCode. Not grinding exactly, but disciplined practice of solving specific, short challenges.
4. Browsing OOP (other people's programs) on github and such.
I'm curious to see what others post!
I've never deliberately practiced or anything like that, just built stuff that I wanted to build.
It is very easy to miss this because invariants are often not written down, and you have to discover them.
Related are preconditions and postconditions. Function A establishes postconditions that satisfy the following function B's preconditions.
Your goal maximizing merit of code is to ensure invariants, preconditions, and postconditions may be stated as simply as possible; and keep them satisfied.
If you don't want to be banned, you're welcome to email hn@ycombinator.com and give us reason to believe that you'll follow the rules in the future. They're here: https://news.ycombinator.com/newsguidelines.html.
An invariant is maintained at entry into and exit from a function. What happens inside the function is usually nobody else's business. You can violate it for a reason, there, if you make sure to restore it before returning.
The role of invariants is much like that of conserved quantities in physics. Things can get very complicated, but energy and momentum conservation mean the amount of chaos is strictly limited.
If they are kept as simple as possible, the code can be more easily understood, and has less scope for bugs.
No, they aren't talking about pure functions. Look into design by contract which brings this idea to the forefront. Invariants are properties that you state about a system that should always hold true over some portion of code. They are related to pre- and postconditions (which are more commonly understood and used, sometimes even explicitly, whereas invariants tend to be less explicitly covered by code).
Pre- and postconditions are properties that hold true before or after, respectively, some segment of code (often defined at function boundaries). Like that an input, even though its type may be int, should always be positive. That's a precondition, you can encode it into some languages, or you can cover it with an assert, or you can just "know" it (far more common). Postconditions are similar, at the end of the computation some properties hold about the returned value or the way that the state of the system has changed.
Some languages (SPARK/Ada, for instance) even let you prove that if some properties are true (basically preconditions become assumptions about the system) then your code will result in the postcondition being true (but not for everything you may want, they're working on pushing the boundaries of what can be proved). And if the system can't prove it, but you believe it to be true, you add other properties about the system or tell it to assume certain things that you may be able to prove by hand (or whatever) but not programatically. Or perhaps the systems proof reveals a problem where the computations will violate one of the conditions. Languages like Idris permit encoding many (most?) of these ideas into the type system which has its own accompanying proof system.
A prover (or anyone with common sense, short example, use your imagination for how this could be more usefully extended) can show that given the assumption of the precondition and the action of the code, the post condition cannot hold. In some languages, without the ability to prove it or even with, you may use asserts (often people disable these for "release" builds because they can be a performance hit, so hope your testing and code reviews are good): Invariants are related to that, they are properties that should hold over some period (perhaps the entire program, perhaps just parts). A common application is the idea of a loop invariant. This is something that is expected to be true about the state of execution of a loop across all iterations. As a trivial example you may have a sorting algorithm (let's not worry about the efficiency of this, brain tired) and you have two indexes (quadratic time worst case). Your outer loop determines the lower bound of the next inner loop: Then you have two invariants: The first portion of the sequence (from 1 to i) is sorted (it may not be the final elements in it, but it is sorted). This holds both before and after the swap. And min is always the index of the smallest value in the subsequence from i to size when we hit the swap. Like I said, this one is trivial, but it's about all my brain can handle right now.You can also see invariants in the way abstract data types may be used. For instance, to use a simple example, suppose...
https://reprog.wordpress.com/2010/04/25/writing-correct-code...
A type may be seen as a convenient packaging and naming of a collection of invariants.
0: I choose not to work anywhere that sells critical infrastructure, medicine, or anything else where imperfections could jeopardize someone's life.
https://web.archive.org/web/20150905163826/https://www.youtu...
BTW, I think sometimes is ok to have inline code with a comment of what the extracted function name would have been.
---
Also, by coding using his TPP helped me gain confidence on figuring out complex algorithms. For example, he explains how writing a sort algorithm went from bubble sort to quick sort (I think) by following the TPP. https://en.wikipedia.org/wiki/Transformation_Priority_Premis...
---
Lastly, as I have a few projects that I can't even run dev on them, because their deps are too outdated, I try to keep deps at a minimum. e.g. I have a custom minifyCSS that minifies ~85% compared to other minifiers, but it's super simple in comparison.
https://github.com/uxtely/js-utils/blob/main/minifyCSS.js
Given that, I do work at a lucrative position doing cutting-edge work.
What made me get programming was- _getting hired_. I was so-so before.
When I got hired, the work was so challenging and the situation was so unforogiving, I had to grow. I had no other way. I learned new stuff, wrote code that inspired me, and was really "impossible" by me a few months ago. Sometimes, a few weeks ago.
What made me and and keep making me better than before is Hacker News and peers.
I want to get as good (from the first paragraph); grow as a programmer, grow as an overall problem-solver and thinker.
I keep learning new paradigms, new applications, gaining new knowledge, and growing as a person.
So the most important thing would be community. Before HN, Reddit. Now, Twitter and Discord, too.
I get to know so many new perspective, so many new resources to follow, books to read, stuff to generally know. I barely have time to breathe.
I also made some genuine, long-term friends through this process.
So, community would be the utmost important things.
I was born in a middle-of-nowhere small town, and community and internet are the ones that made me who I am today.
I am not much, but definitely levels above what I was a decade ago.
Edit: It would be incomplete if I did not mention high-quality teachers teaching for free through MOOCs, YT playlists, etc. And internet pirates, too. Having quick access to any book you might want is very important. (I do spend a lot on books, but piracy is what makes me spend that money so that authors get what they deserve).
So, I become a better programmer when I'm actively working on something. I no longer contribute code on a regular basis at my day job so to stay sharp I've started creating little utility programs for personal use.
I've also started finding hobbies that incorporate some programming, generative art, robotics, etc.
tldr; find ways to incorporate programming into everyday life, practice by building things that are actually useful to you.
- Studying and researching best practices for not just your current language or paradigm, but others as well. If you've got experience with Java, look into other paradigms than OOP such as Haskell / Elm that focus on Functional Programming. This can lead to learning about new ideas of programming in general, which is a good thing as it exposes you to more tools that you can use.
- Making your own things. Find areas in your life that could use some automation, or perhaps use programming as a creative outlet; the more tools you make, the more practice and experience you'll get which leads to the first point I made.
- Cringe at your old code. If it's really that bad, it's likely you've come a long way as a developer, which is good! Reflect from time to time about why you originally wrote code a certain way, and think about how you would approach the same problem now with more experience.
* Programming competitions. The main utility I got out of this wasn't learning obscure data structures or algorithms (which tend to often not be very relevant), but rather the experience in coming up with corner cases that might break my own code, and how to write and debug code without sitting in front of the computer. I guess in short it's the ability to separate the algorithm from the implementation and track down failures to one or the other.
* Going to school and getting a CS degree. Learning in general is useful, but I believe that the structure of a full CS degree is generally better than picking things up ad-hoc, especially in guiding you towards areas you wouldn't otherwise think to learn about (e.g., I wouldn't have picked up information visualization on my own).
* Seek out and learn as much as you can in general. You never know when randomly learning some topic in some distaff field might come in useful; one time, I happened to discover (while looking up information on ptrace in a debugger-like side-project I was working on at the time) that Linux supported hardware watchpoints via perf... and a few days later, we had to resort to that to track down a bug that wouldn't reproduce in gdb.
* Peruse other projects for how they do stuff. Part of the above, but you'll pick up on "huh, that's an interesting thing to do" as you learn about how they work. If you're curious about how something works, just open up the source code to see how it works. At the very least, doing so frequently will give you a good sense of how to quickly find stuff in large, unfamiliar projects, which is apparently not as common a skill as I take it to be.
He helped me establish many good habits that helped me to grow throughout the years. More than 3 decades later we are still good friends.
i made toy apps and widgets. chrome extensions. took on a bit of client work.
read some books.
stay up all night and hack on projects for a few years while people are sleeping and youll get some where.
never had a sideporject or 'startup' that actually made money, but thye taught me the skills i have now that are paying the bills, and i pretty much have my pick of jobs at this point.
but also suffer from extreme impostor syndrome and try and use that to fill my (perceived) gaps
- Learning new languages which had vastly different paradigms than the ones I was used to. Java to Perl to Javascript to PHP to Erlang to Clojure to Go to... Over time you learn patterns from one place you can bring to another, and learn patterns which exist in most places which you'd rather didn't.
- Lots of experience in the actual workforce, building things people (supposedly) wanted. There are aspects of programming which people spin their wheels on which really don't matter in the long run, and conversely there are aspects which go ignored but are very important. Working on real, rubber-to-the-road projects gives you perspective on what actually matters.
- Being a daily archlinux user (I don't use any non-archlinux OS on any machine I own). Yes, it's hard. Yes, it's an incredibly good use of your time to figure out. Once you're comfortable in arch, every other Unix-like OS (ie, most of them that you'll ever probably work with) will feel familiar.
Being really interested and motivated to write my own projects.
Learning everything I possibly could about every topic I found interesting relating to computing ans software.
Focusing my effort on a small selection of technologies.
Making the decision to be able to build an end to end application on my own.
When I needed to make SunOS give me tty key stroke input 1 character at a time, he refused to tell me what he already knew and just pointed me at some "areas to look into". Knowing about kernel tty drivers and terminals and line buffering didn't make me a better programmer, but it didn't hurt. Knowing my way around a variety of different documentation at many different levels of the systems I was working on helped make me a better programmer, and my mentor refusing to just answer my questions was a major factor in that. It also forced me to understand much more of the systems I was working on, rather than just how to use some piece of convenient API.
One of the engineers under me often expresses his discontent that I won't give him answers, either. I think he's more than able to get to those answers on his own, and that the value he'll derive from learning how to learn is vastly greater than the value of being able to complete his work faster. On the other hand, he's given me the feedback that the method is excessively socratic at best, and flat-out frustrating at worst.
You must sometimes give the answer.
> he's given me the feedback that the method is excessively socratic at best, and flat-out frustrating at worst.
Yep, exactly how I felt in 1987. It was totally worth it, viewed from the present day.