How to learn programming in general?

Hi, I have been writing code since I got a C64, but I realized that I never learned to think like a programmer. I think that is the reason why I still have trouble writing GOOD code.

What can I do to improve this? Just reading programming books does not help unless it is the right book that explains how to think like a programmer. Simply following the tutorials or writing code at random won't help either, as long as you don't learn what is important when programming.

What can I do to become a better programmer, regardless of language? How can I train my skills to think more like a programmer?

I hope you understand what I mean

Greetings
Markus

1 Like

In my experience you learn best from your own mistakes, and you can only make mistakes by writing code.

First of all, this is not a Rust-specific question, as interesting as the question may sound. I can say, writing Rust certainly doesn't make you automatically a good programmer just by using it.

Other than that, aside from the obvious requirement of being able to think logically, I think this may turn out to be a very philosophical question due to its abstractive nature. This is definitely not the right place to get into philosophy.

My only recommendation is, improve your logic skills. Programming is all about knowledge of your inputs and corresponding outputs and translating that knowledge into a program.

There are a handful of principles to keep in mind at all times while writing software, but the only way to "learn to think like a programmer" is by being a programmer and doing what a programmer does. Writing code. Lots of code. Meaningful code. Code that you give a crap about, because you're solving an issue with it that is important to you - personally. Code that needs to be efficient and easy to maintain, at the same time. When you're trying to create one program after another to solve problems in an optimal way, while at the same time making sure that you, as well as other people, will be able to come back to it later and introduce the necessary changes, because you want your application, library or the entire project, to behave and do what it's supposed to do properly - you're unable not to learn to think like a programmer.

With that said, every language is going to have its features, quirks and peculiarities - so while it makes sense to begin with a general set of principles, in practice you'll always have to consider the most "idiomatic" way to do things in a particular language. That's not something you can simply discard.

I'm not going to give you any specific recommendations - since you didn't ask for anything specific. Programming can be compared to painting. There are street artists and there are people painting on small canvases. Each of them is specialized in their own thing. Programmers are identical. The way a front-end developer is going to be thinking of his programs is not going to be identical to the way a programmer of embedded devices is going to approach his code. Each of them is going to have their specific set of principles and rules of thumb, related to the field of their choice and preference.

Instead of focusing on all-encompassing notion of "thinking like a programmer", I'd suggest to start by considering your interests. Are you into back-end? Embedded development? System programming? Front-end? Machine learning? What kind of problems do you want to solve?

Once you've got that out of the way, see what you've got to know and understand in that particular field of your choice. "Thinking like a programmer" is not going to help you much in coding neural networks if you've got no clue what deep learning is all about. Get acquainted with your field, as much as possible.

Once that's done, look for the programming language(s) that is (are) most suitable for the problems you want to solve. ML? Python. Front-End? JS. Embedded? Well, Rust can help. Back-end? Pick any.

Then get acquainted with the existing projects. Study their structure. The way code is segregated. Naming conventions. Idiomatic expressions. See how the code of beginners differs from the code of experienced developers. Learning by contrast is much quicker, at times, than many other things.

Supplement your practice with tutorials and books. Whenever you see a piece of code, ask yourself: why was it written that particular way? It does what it does, that's good. But why did the person choose to write it the way it was written? Can it be written better? If yes - how? If not - why? The worst thing you can do while trying to learn the "programmer's mindset" is to copy&paste. It's fine if you need to solve an issue you have in the shortest amount of time possible. It's not fine if you're trying to improve.

Other than that - just code. Code on your own. Code with others. Code in a team. Code with git. Code the kind of problems that you want to solve on a daily basis. You'll learn to think as you go, naturally.

5 Likes

I suppose that logical thinking is not my problem. I wrote an A* pathfinder to move mobs in a Minecraft-like voxel game called Minetest in Lua, including steering behavior.

You can see the final result in this video

I was almost happy with the result, but I wasn't happy with the code because it was written relatively naively and straight forward.

I also wrote some websites in PHP and Python, some other game mods in Lua and a few embedded Projects in C and ASM.

One problem is, that most books, tutorials or videos just teach you the syntax of a language. They don't necessarily teach you how to write good, maintainable code. And if no one tells you, that you are doing something wrong, you will continue to do it wrong, no matter how long you try.

Also, most books have an emphasis on syntax, but I have recently learned that it is more important to understand data structures and the algorithms to work with them. If you don't know what kind of data structure you should use in a particular situation you will end in some messy, slow code.

In the last few month I realized that I'm doing something wrong. I accidentally watched a video from Andy Harris "How to think like a programmer". That opened my eyes (at least a bit).

Since then I tried to find resources to get deeper and I learned new things I never heard before. I never heard of principles like DRY because no one ever told me this. No one ever told me to analyse a problem and split it into smaller parts and write an "algorithm" to solve each of this parts.

This is how I understand "how to think like a programmer":

  1. Understand the problem
  2. Split the problem in smaller parts
  3. Write some kind of algorithm as detailed as possible, e.g. as comment in the source file
  4. Fill the space between the comments with code

Maybe a bit simplified, but I think you get the point.

But it is still difficult to "reset" my brain to use the new stuff. I still write naive, straight forward code.

I want to "restart" my coding career with Rust. And I want to focus on console tools and embedded programming. I've chosen Rust because It is the only usable language for embedded systems besides C/C++ and it is not object oriented (I cannot cope with this).

I'm not sure what has stopped you developing the aspects you seem to be reasonably aware that you want to improve. I began decades ago, like you not knowing anything about "how" to code. I just got more and more into doing it, and at each stage developed skills and techniques for tackling more ambitious things.

I never liked learning from books, so like you tend to mainly use them for reference on syntax rather than technique.

I was sent on one week long Learning Tree course after joining the Software Group of my employer. I think that helped a lot by showing me basic techniques and mental tools for designing programs, so maybe a book on that would help. I don't know any to recommend unfortunately.

Apart from that I learned by doing, but during my career was often working with people who knew more than me, so I must have picked up useful ideas and watched how they did things to an extent. Mostly though, I think I learned how to tackle different problems by doing. Sometimes you need to sit and think about how best to solve something, and maybe test out ideas in code, sometimes you can just get on and code.

These days I can often go straight to code, but I know that is because I've developed a good sense of how to do things and no longer need to spend as much time thinking. Although not long ago I did have to do that, and spent lots of time writing notes and drawing diagrams, so it always depends on what I'm trying to do.

From the sound of things you want to make a career in programming, and may have selected a Rust because of that. I'm not the best person to advise on how to get on career wise though so don't want to comment on that just now but maybe others can, if you say a bit more about your experience and goals.

Programming was most times a hobby. As I said my first experience was some Basic code on the C64. I never had a programming course or similar. I had some projects as an employer, but never in a company with other software developers. All programming skills I taught myself.

Now I work in a company repairing embedded devices, and I want to write code to analyze these devices. It's not the problem to do this with my current skills, but I want to evolve because the way I code is, as I said, naive and straight forward

1 Like

I think we should start from what your definition of "good code" is. At the most basic practical level we can ask:
a) Does the program logically do what I want. Or what some spec. demands?
b) Does the program not do what I don't want it to?
c) Does it do all that with the performance required?
d) Often for me, does it fit in the memory available?

If the answer to all that is "yes" then it is good code.

After that we get into some very nebulous territory:
e) Is it understandable to those who may come later to fix/extend/port it. Or even myself later?
d) Is it easily extendable, modifiable, portable?

Here we get into a swamp of advice about DRY, SOLID, Design Patterns etc. Countless books on the topics.

The problem is, once you get past the requirements of the here and now you are contemplating some possible requirements in the future. Which is of course very unpredictable.

I have come to the conclusion that nobody knows. No matter what they claim. For example Object Oriented Programming was de rigueur for a couple of decades. Today there is a tangible rebbelion against it.

When I started out "top down", structured design and programming you describe was the way to go: Understand, split, design, code. For a long time now overpaid consultants have been touting "agile" development instead.

I gave up hope of being a "good" programmer decades ago. It's impossible to keep up with all the fads in real-time. I just make stuff work. If I can do it so that the code reads like a coherent novel all the better. Of course I follow company standards, conventions, etc when I have to.

Bottom line is. Learning by doing. Learning by reading what others have done. It really helps if you can get to work with other skilled, experienced, programmers. In one second they can explain why doing this thing is better than doing that thing, usually by pulling apart code you have put up for review. All what they say may well be in books somewhere, but you may never find it or not understand the significance if you do.

My current advice is:

  1. If at all possible split things up into functions/modules even classes that are meaning full and possibly useful by themselves

  2. Which implies minimizing coupling between those functions/modules/classes.

  3. Minimize the amount of comments you write. Comments are extra maintenance work, change the code you need to change the comment. Which means they often fall out of sync with reality and are wrong. More confusing than helpful. The compiler cannot check them.

When you find yourself writing comments ask yourself why? If you find yourself writing something like:

// Read widget.
    // Loads of code... 
// Process widget data
    // Loads of code... 
// Write other widget
    // Loads of code... 

That may already be a clue that you have big chunks of code that could be pulled out into meaning full functions/methods

  1. Give things sensible names. For example those steps above could become "read_widget(widget)" or widget.read() or whatever. At that point the way you have created and named the functions already says what you previously wrote in comments. Make the comments redundant.

Naming things meaningfully is said to be one of the hardest things in computer science. I find that when I start thinking about what a thing should be called, different name choices change how I think about what the thing really is, or what it should be.

OK. Enough of my rambling.

4 Likes

many great points already mentioned...

i have to highlight ability to look back on own code after some years (months), wondering what kind of mor... cough enthusiastic programmer wrote that thing, and have a good nostalgic laugh after realization of ownership... (happens to me frequently)

Other than that - "clean code". Sometime many book/sources can sound too extreme or specific to domain/language, but taking a bits of advice here and there helps many times. Furry cat debugging is great clean-code helper for me, (impl rubber-duck-debugging, as i don't have the duck)... :slight_smile:

:thinking:

Would you prefer code that is clever and arcane? Surely all programmers should write code that is as straightforward as possible.

I mean, I recognize the impulse to write code that is extra clever, and I occasionally succumb to it myself, but I try to rein it in for code that I write professionally because the best kind of code to read is the code that's so utterly simple that the average bear can understand it.

3 Likes

I was wondering what was meant by "relatively naively and straight forward"?

It could mean not "clever". In which case I'm with you. Code should be a plain and clear as possible. Unless there is some pressing need otherwise. If one ever finds oneself thinking how to reduce 5 lines to 3 or get as much as possible done in a single line, please stop.

On the other hand in could mean something more insidious. One starts out coding in what seems a straight forward way, step by step. All the while thinking "OK, I need to do this so I will write that... OK, next thing..."

After some tens or hundred hours one realizes that this step by step approach has produced an giant incomprehensible mess. Every step of the way felt like the right direction but one ended up in the wrong place. In bad cases realizing one cannot even get to the end of the journey from here and it would be better to throw all away and start again.

Commonly known as "painting oneself into a corner".

The naivety here being that one did not see the big picture from the outset. One wanted to climb a mountain but in those little steps, up hill all the way, ended up stranded on a lesser hill.

I have no idea how anyone can be taught this, it comes from a lot of experience.

It leads to the oft' given advice "be prepared to write it twice".

4 Likes

I found the PingCAP Talent Plan to be a good way to improve my style. I would work through an exercise and then compared my code to the provided solution. That approach proved to be better than just reading code, because my brain was deeply involve in the solution.

1 Like

I care a lot about writing code that is easy to understand, and this thread reminds of an experience from my job I'm not quite sure what to do about. I wrote an API that one of our customers is supposed to use to transfer various types of user data between their and our systems, and it has some ... uncomfortably complex requirements. The first MVP I wrote was a spaghetty-mess, but I rewrote much of it in a manner with several smaller self-contained pieces of code that should individually be easy to understand, and then I implemented the API by composing these pieces.

However since I work part-time outside of the summer vacation, another developer has taken over the main part of the project (I wrote it during his summer vacation). As far as I can tell, he has no trouble understanding each individual module in isolation, but when he has tried to make a larger change, he had a hard time figuring out where to look for the problem. I guess the composition is what is difficult here?

It's not like this is a bad developer either. I really wish I knew how to avoid this, because I really do value understandable code quite a lot.

3 Likes
DISCLAIMER

The following paragraphs express my personal opinion, experience and belief. I am not going to be able to prove anything what I write and if you disagree, I'll gladly accept that as-is, because having differing opinions is natural and the basis of our society. I am not interested in any heated debates, that tend to escalate into "religious wars". Also, please try not to change my opinion to your opinion. If you can provide scientific proof, that the conclusions I ended up with are wrong, by all means do that. That's not the same as trying to force your opinion on me, because opinion leaves space for diversity and interpretation while scientific proof is absolute. I'll just ask you to be civil and not condescending, if you do so. Thanks for reading this!

A good way to assure code is maintainable is to provide a complete list of requirements of the code, as well as the reasons behind the way it was written.

After all, we cannot read other people's minds just by looking at their code. Different thought patterns make it hard to interface with other people's code, unless you've worked with them long enough to grasp how they tend to write the code to be able to navigate through it and understand it without documentation.

Many solutions exist to any given problem and even if you solve the same problem in the same way, there are many "accents" for the same programming language. While we have a standardized way of formatting our code via rustfmt, we do not have a standardized way of organizing our code; that is to say, we don't communicate with a common accent.

While it is technically possible to automate code organization, the tool that comes closest to it is clippy, but that tool doesn't make any changes to your code by itself and is not nearly as aggressive with normalizing code as it could be.

I guess, the next step after having agreed on a standard way of formatting our code and having a tool which enforces this standard is to agree on a standard for organizing our code and make a tool that enforces it.

People will likely fight for their one and true way of code organization, just as they did for their preferred way of code formatting. In the end, after exhaustion due to all the fighting, we get to the point where we'd rather just have homogenous code, always formatted the same way and organized the same way, not having to learn several hundreds of different accents to understand every bit of larger code base there is.

Personally, I just want to be concerned with the important, actual problem at hand and not having to learn how someone's code is formatted and organized, first. That's not the purpose of programming!

1 Like

Well, maybe one reason for this is some code I've seen in my company (not written by me). This is literally spaghetti code, written 10 or even 15 years ago and hard to maintain.

This code needs to be refactored or even rewritten. But this will never happen because it costs time and time is money. The fact that the maintenance and the bugs also cost money is not taken into account.

I want to be able to write better code than this. I know that this takes time and a lot of practice, but it takes longer if you have no direction and just stumble around.

I agree with @Phlopsi about everyone solving a problem in a different way. In a former job, a group of us was asked to vectorize some scalar Fortran. The task was mostly mind numbingly dull, so we amused ourselves by trying to tell who had written a given routine. By the end of the project we had gotten pretty good at it.

1 Like

Yes. That word "composition" is a trigger for me. It generally means that whatever follows next is totally incomprehensible.

First order of the day is that I want to push data into your API, have it do whatever it does, and get data out.

I don't want to "compose" it with anything.

This first hit me a decade ago when some C++ library we were using required deriving a class from whatever base class the API had and implementing a bunch of methods to get it to work. All totally unnecessary busy work.

I guess there might be a new generation weaned on "Functional Composition" now a days for whom this all comes naturally. I have not met any yet.

Or maybe I have not understood what you are getting at....

In this particular case composition means to read the request data into some sort of object. Read some data from the database into another object. Combine the two into a combined object. Write the new object to the database, and finally create some sort of response, also based on the combined object.

The composition refers to having a file that calls out to a bunch of otherwise independent modules, tying them together.

1 Like

Aristotle said:
"Practice or practice is an action, the purpose of which is itself, is superior, for having an end in itself, and therefore sufficiency.
Theory is also practical; they are opposed only as to how much theory is supreme practice, unlike what is only practical, but does not become theoretical."
In this sense, I think you should advance the theory, perhaps a data structure course is a good way, the book "The Art of UNIX Programming" may be another, or an elementary course like this https://www.coursera.org/specializations/computational-thinking-c-programming

I think I found a good example for what I mean with "naive programming"

If I had to solve this problem, I would try to parse the actual word instead of reducing the problem. It's not wrong, but also not good.