I'm joining a bit late and I see some wonderful answers already, but let me share how I understand what readable and maintainable code is.
The terms readable and maintainable go hand in hand, although they may be seen as two distinct parts. We all, as developers strive to write understandable and easily extendable code and most of the time we fail, at least on the first try.
"Why?", you might ask. Because writing readable and maintainable code can be hard. In a team, factors like deadlines, unestablished code styles and even value conflicts can be the cause of hardly readable and unmaintainable code.
When we speak about our own code though, things can be a bit different. First of all, you need to know what is wrong with your code to be able to identify bottlenecks and evaluate the readability and maintainability. I try to follow a few principles while coding:
- Make it work, make it right, make it fast - it's the famous quote by Kent Beck. First, write out code that handles one common case (make it work), then fix all special cases and error handings (make it right), so all test pass and just then think of a way to further optimize it, if possible (make it fast).
- Meaningful names - your code should be able to be read as a chapter of a book. Write code that humans will and can read and comprehend and avoid any mental mapping. Always use searchable names. Try to shorten the names, as long as they are clear enough. Pick one word per concept and avoid using the same word for two different things.
- If you can't understand a piece of code in 5 seconds, it's probably a smell. - I saw this quote in one of the John Papa's presentations. It's so true it hurts.
- Keep functions and methods short - taken from the Unix philosophy, your functions and methods should "do one thing and do it well". What's the best way to achieve it? Keep functions and methods short, as short as possible. Try to keep the side effects to a minimum.
- Control code abstractions - How many times have you become lost in a code with 6-7 levels of abstractions? Abstractions are a great way to hide complexity and duplication but are overused in so many cases. Sometimes it is better to just write one more line of code instead of extracting it in a function somewhere in the code base. The more straightforward the flow is, the better.
- Follow established coding standards and build upon them - I'll just repeat Jonathan Cutrell's words. Follow coding standards and practices, you can even build your own, based on your needs. Architectural principles like DRY, SOLID and KISS are here to help you, so utilize them.
- Keep cohesion high and coupling low - this is correlated to the preceding points. Try to make your modules independent, they should not depend on each other. Cohesion, on the other hand, refers to the degree to which elements of a module belong together. The related code should be close to each other.
- Always come back to your old code - I left this one last for a reason because we almost always forget to do it. Check your old code from time to time, iterate over it, refactor it, improve it. You will most certainly find something to fix. It's a great exercise to see how are you progressing and learn from your old mistakes.