-1

I am currently making an html-based editor, i know there are lots of editors out there especially html based but i want to do it myself once. What i am concerned about is, for e.g, when a user press a key, following operations will happen.

  • check if some text in the editor is already selected, if selected delete that text first. and push this action in undo stack.
  • input this character which also includes manipulating the array and then re rendering the line.

Now these two tasks alone can include so many function calls, like deletion alone will check which text is selected then it delete that text which will manipulate the arrays and re-render the lines and so on.

If I break down this code into a readable form, there are lots of function calls on just a key press and u know user does not have to just press on key, if I dont break the code into a readable form this will make the complex and a lot of code will be repeated. What is the good practice ? to make the code more complex and have less function calls or to make the code readable and have more functions ?

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • 4
    More functions and readable code! https://sites.google.com/site/unclebobconsultingllc/one-thing-extract-till-you-drop –  Jun 10 '18 at 21:02
  • @CertainPerformance it is a performance bottleneck, though i understand ur point. and i myself write a code in a more readable way. –  Jun 10 '18 at 21:05
  • 2
    If performance *is* a serious issue (wasn't mentioned in your question), then you may have to sacrifice some readability - but in most cases, performance isn't worth considering. –  Jun 10 '18 at 21:08
  • @CertainPerformance the link u shared has some great comments worth noticing, i did not notice that i had this habit naturally, anyways thank you for the link –  Jun 10 '18 at 21:12
  • 1
    The number of functions and readability are not that closely related. You could for a class structure, or behaviors composed out of functions. – S.D. Jun 11 '18 at 04:19
  • 1
    Possible duplicate of [Refactoring into lots of methods - is this considered clean or not?](https://softwareengineering.stackexchange.com/questions/94429/refactoring-into-lots-of-methods-is-this-considered-clean-or-not) – Doc Brown Jun 11 '18 at 06:03
  • 1
    Too many functions is less readable typically. But we cannot generalize -- either extreme may be unreadable. – Frank Hileman Jun 12 '18 at 21:39
  • This is actually something that come with experience - where to put the cuts in the code flow. I would suggest putting an upper limit on how much code can be in a single method, and when you reach that then break up the method. If you place the cuts well, the method names come easy. If they don't then reconsider what you are doing. – Thorbjørn Ravn Andersen Nov 02 '21 at 17:12
  • Some languages and their respective compilers offer features that allow function calls to be inlined in the calling method, at compile time. For example, in C#, there is `MethodImplAttribute` that can be used in a method to inform the compiler that such method should be inlined in the calling code, whenever possible. I don't write Javascript that much, but I believe there should be tools that optimize Javascript files and that might provide similar functionality. – Unknown Coder Nov 02 '21 at 17:13

5 Answers5

7

Every time you should try to make code as readable as possible. Modern compilers can optimize code to make it more or less as fast as possible so you don’t have to think about it. Or if it is needed, you can optimize it later once your algorithm is written and understandable. Always remember, code is written once, but read many many times... There are many books regarding this topic, but my favourite is Clean Code by Robert C. Martin. He has also a wonderful blog at https://blog.cleancoder.com/ Hope this helps!

Xenon
  • 79
  • 2
  • 1
    I like this answer but it's missing some critical points. Optimization shouldn't just be flat out ignored. It should be put off until a test shows it's needed. – candied_orange Jun 10 '18 at 21:32
  • i also have a question, does your answer also applies to the javascript vm like node in chrome ? or firefox ? – Mamoon Ahmed Jun 10 '18 at 22:11
  • @candied_orange yes, I have changed it now, in general, you should start optimizing the code once it is at least functional and understandable and only if it is needed. – Xenon Jun 11 '18 at 06:22
  • @MamoonAhmed In general, yes, it applies to all compilers, VMs etc. But some compilers do it in better ways than others of course :) – Xenon Jun 11 '18 at 06:23
  • @candied_orange Simple, straightforward code is very often the best optimised because it avoids unnecessary work. That kind of optimisation should be there from the start. – gnasher729 Jun 11 '18 at 07:56
  • Note that even afterwards, optimization is tricky. Modern interpreters, in particular (including anything on a JVM), optimize on-the-fly, and often in nondeterministic ways, based on actual usage patterns. Even careful analysis in a production-like environment may not tell you the truth. Here's a link discussing a talk by Joshua Bloch on this topic: [Joshua Bloch Performance Anxiety/DZone](https://dzone.com/articles/joshua-bloch-performance). – schnitz Jun 11 '18 at 17:03
  • Absolutely the worst reference for readable code. – Frank Hileman Jun 12 '18 at 21:40
  • @FrankHileman I agree that sometimes it is not ideal, but I think readability is quite individual for everybody. It is like with writing. – Xenon Jun 14 '18 at 11:08
  • @Xenon Exactly. This is why each developer believes their code is more readable, and other developers' code is unreadable. However, like API usability, readability tests could be constructed, though the test subjects' past experience would have a huge bias on the results. – Frank Hileman Jun 14 '18 at 16:09
3

Readable code, and the right number of functions. More or fewer functions doesn’t affect readability, deviating from the right number does.

In your editor, avoid special casing. Don’t write code for pressing a key. Write code for replacing an arbitrary range of text with an arbitrary new range of text. It’s difficult. The code for it will be difficult. But that’s only once.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
1

From a code maintenance viewpoint, since we don't write write-only code, think about:

  • Complexity
  • Modularity
  • Maintainability
  • Reusability
  • Testability
  • etc etc

A single complex procedure/function is less reusable, less maintainable, and less testable than a set of separate procedures.

Quite simply, a function should perform a single function; a procedure a single procedure... putting multiple activities into a single procedure increases all sorts of risks that you get something wrong.

Furthermore, the more complex a procedure is, or the more activities it undertakes, the higher the number of test cases you will need to perform.

ISO/IEC 25010 defines a whole load of quality characteristics... they are there to help you improve the quality of your code. Do not get obsessed with specific thresholds, but aim to keep the measures within acceptable bounds.

Andrew
  • 2,018
  • 2
  • 16
  • 27
0

I would recommend you to consider separation of concerns principle, which will make your code readable and without side effects as much as possible.

deletion alone will check which text is selected then it delete that text which will manipulate the arrays and re-render the lines and so on

all these operations ideally should be atomic. If you are familiar with reactive programming, your task could be solved like this:

keyUpStream$
.map(findSelection)
.map(removeText)

editorChange$
.do(takeHistorySnapshot)
.do(render)

these functions should be atomic, and you can use them to build other handlers as well. And as you see, it is clean and readable.

And I would suggest you to take a look at how such problem is solved by other editors, like codemirror for instance https://github.com/codemirror/codemirror there are interesting take aways.

Pavel
  • 111
  • 1
0

Side effects require a firm grasp of a bigger picture than just the function causing them for programmers to do things predictably and correctly.

From my standpoint, it's a balancing act if mutations/side effects are involved. If you spread the logic of mutating your central application state across many teeny functions, then the functions themselves become easier to comprehend individually but can make it more difficult to reason about what's happening to the application state combined together as a whole.

Bigger pictures are difficult to grasp when fragmented into small pieces.

It's like fragmenting a picture into many small puzzle pieces you have to put together to figure out what's going on when the relevant picture you need to grasp to do things correctly would have been easier to grasp if it wasn't fragmented into many teeny pieces. In such cases, the small picture becomes easier to grasp but the big picture can become harder to grasp.

And as long as your functions aren't pure and invoke side effects (this includes methods that change an object's internal state), then you generally have to be able to comprehend many functions taken together as a whole (a bigger picture) in order to effectively reason about your code from a bird's eye view (and especially so if your codebase is multithreaded). If you fail to understand the bigger picture, you risk finding yourself in integration hell where everything seems correct taken individually and your unit tests pass but they malfunction in confusing ways when combined together.

... but pure functions don't require such a grasp of a bigger picture.

If, however, you write lots of teeny pure functions that cause no side effects, then I think it no longer becomes so important to comprehend what they're doing when combined/interacted together to reason about the bigger picture (most blatant example but not limited to this: to reason about the thread-safety of calling your functions from different threads).

A temporal coupling example:

Take a basic example of temporal coupling with an object that requires calling method A before method B can safely be invoked. That order dependency that allows them to fail when called in the wrong order -- even if they're correctly implemented individually -- only exists because it was diced into two separate smaller functions that involve side effects (mutations to state shared between the two).

If we eliminate the mutations to state shared between the two, then there's no order dependency between the two. If we keep the mutations, then the way to eliminate the temporal coupling here is to actually combine A and B together into a larger AB function. There is an argument to be made for fewer and larger functions when side effects are involved, even if they compromise readability a bit, but I think the ideal approach is to try to limit the number of functions that have side effects in the first place.

Personal experience:

What I have found most productive over the years is to favor the minimum of functions that cause side effects in large-scale codebases. That may or may not imply larger functions, but definitely fewer of them. Then we can go happy designing as many small utility functions as we like provided they are pure and free of side effects. One-liner functions are fine and productive to me as long as they're pure functions. People's mileage will likely vary. But for my field and domain, it has been invaluable to reduce the number of functions causing side effects to the bare minimum (ex: to have objects with as few methods as possible that change their state, which makes it easier to reason about their ability to maintain invariants), and even if that sometimes comes at the cost of writing a function that spans a whopping 100 lines of code. But I try most of all just to avoid writing functions with side effects in the first place.

As a caveat, I work in a real-time field where it's vital to keep at least 30+ FPS (ideally 60+ FPS) for the user experience. I'm also among the most paranoid types on teams when it comes to testing and being able to reason about what we're doing from a bigger integrated picture than what our unit tests can usually suggest. We also do a whole lot of multithreading. Multithreading combined with some heavy demands for efficiency especially adds pressure to reduce the number of functions/methods with side effects in the system in our case. We would probably be in a constant state of panic and constantly tripping on race conditions if we didn't reduce the number of functions in our system that cause side effects to our application state to the bare minimum. I share similar thoughts and experiences with John Carmack here:

The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline [commentary: he is talking about using bigger, fewer functions referring to manual inlining] does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don.t let them slide back into impurity!). [...] Currently I am leaning towards using heavyweight objects as the reasonable break point for combining code, and trying to reduce the use of medium sized helper objects, while making any very lightweight objects as purely functional as possible if they have to exist at all. [...] Besides awareness of the actual code being executed, inlining functions also has the benefit of not making it possible to call the function from other places. That sounds ridiculous, but there is a point to it. As a codebase grows over years of use, there will be lots of opportunities to take a shortcut and just call a function that does only the work you think needs to be done. There might be a FullUpdate() function that calls PartialUpdateA(), and PartialUpdateB(), but in some particular case you may realize (or think) that you only need to do PartialUpdateB(), and you are being efficient by avoiding the other work. Lots and lots of bugs stem from this. Most bugs are a result of the execution state not being exactly what you think it is. -- John Carmack