3 ms·
> The “hide the state from the programmer” option chosen by OOPLs is the worst possible choice When you code C and write something as simple as fwrite( "H
by Const-me 8y ago
> The “hide the state from the programmer” option chosen by OOPLs is the worst possible choice
When you code C and write something as simple as
fwrite( "Hello world", 1, 11, stream );
that line of code changes state of the file stream object in CRT, state of file caches in OS, state of B-tree nodes in file system driver, state of disk firmware, state of NAND flash chips…
It’s not just OS and drivers. Any sufficiently complex software is built by layering abstractions on top of each other.
Many problems that need to be solved have very complex state, often related to IO or GUI. You don’t have other choice but to use some form of OOP and hide lower-level implementation details behind abstractions, otherwise you won’t be able to get anything done due to overwhelming amount of state to reason about.
- andy_ppp 8y agoNobody is saying don’t have any abstractions, just that your program hiding state in layers of objects is often counterproductive.
- Const-me 8y ago> program hiding state in layers of objects is often counterproductive Do you think it was counterproductive to hide OS-specific file handles, CRT level of caching, and many other things CRT does behind these opaque FILE* object pointers? Such hiding allowed to have very similar and quite easy to use <stdio.h> across all platforms. The implementation of these fopen and fwrite functions relies on kernel calls like open/write on Linux and CreateFile/WriteFile on Windows. Do you think it was counterproductive to hide all the lower-level stuff like OS caches, inodes (linux) / NTFS (windows), and the rest of them, behind these OS kernel calls? If you’ll decide to unhide them, your OS won’t have any security at all, the OS file system cache is security sensitive. Modern OSes implement strict access control while they hide that piece of state, every file has permissions, every process accessing them has user identity, and the OS checks these things when process access files or file system cache.
- jstimpfle 8y ago> Do you think it was counterproductive to hide OS-specific file handles, CRT level of caching, and many other things CRT does behind these opaque FILE* object pointers? To answer that question for parent, no, he obviously doesn't think so. And yes, streams are basically OOP (they are implemented using "method dispatch"). Streams are one of the few successful abstractions out there. And when I say "successful" I mostly mean "necessary". Because they're leaky. Consider fseek(3), ftell(3), fileno(3), fflush(3), fclose(3), fsync(2), posix_fadvise(2), isatty(2), fcntl(2), ioctl(2) and many more. All operations that are not supported for all streams, but necessary for some (like file system files, network connections, terminals devices...). Essentially when you use any of these you concede to break the abstraction. It's incredibly hard to handle these streams correctly, and the vast majority of programs are faulty in that they don't handle most error conditions correctly. In some cases, it isn't even possible to handle errors through the API! See close(2) So again, not saying we shouldn't have a stream abstraction. But abstractions come at a huge price, and therefore it's incredibly misguided to make every program a layering of thousands of pseudo-isolated objects that bring their own bug-prone abstractions and idiosyncrasies.
- Const-me 8y ago> Streams are one of the few successful abstractions out there. I don’t think they’re few. I think all modern software uses OOP-based abstractions heavily. For example, for GUIs, it’s visual trees everywhere. HTML DOM is the most widely used one now, but pretty much every GUI framework have something conceptually similar, even 30 years old WinAPI is built on very OOP-like concepts, you have HWND handles but the state is completely hidden and can only be accessed by calling functions. > Because they're leaky. No abstraction is 100% waterproof, but some are useful enough for many practical applications, despite they leak occasionally. These particular file I/O API is less leaky on Windows, but they still leak, and there’re many weird low-level APIs to bypass these abstractions: DeviceIoControl, defragmentation API, backup API, and many others. > abstractions come at a huge price I’m not convinced the price is > 0. We can always skip any abstraction we don’t like and use the next lower-level one. We don’t often do that because developing code based on these lower-level things is often more expensive than using the abstractions, despite they leak. That’s why people use Electron & JavaScript, Java & .NET, game engines. They all made from large count of the layers you’re talking about, and when they leak that’s indeed can be very expensive to diagnose and fix/workaround. However, dropping them and developing something from lower levels is usually way more expensive.
- Const-me 8y agoAnother example why hiding state can be a good thing. Consider following code: void* ptr = malloc( 32 ); No OOP is involved here, yet the malloc function operates on a critically important piece of mutable global state. The state is quite complex due to numerous reasons: performance, fragmentation, multithreading & deadlocks, alignment… Just look at ptmalloc (linux) or jemalloc (BSD). I think it’s undocumented on Windows but I’m sure it’s at least as complex, search for “low fragmentation heap” which is the default one since Vista. I don’t think hiding that state in layers of abstractions is counterproductive, I think it was good, due to the following reasons. 1. In 99% cases makes it easier to write code. Heap state is very complex in practice, and just like file IO, it spawns across user mode state of your process and OS kernel state. Hiding the state all together allows programmers to reason in terms of very simple APIs, it’s just 2 functions malloc/free with very clear purpose each. 2. Hiding that state makes bugs in your code less likely to corrupt it. If you instead have something like void* malloc( size_t bytesCount, struct HeapState *processHeap ) it becomes easier to accidentally corrupt that state. 3. Hiding that state decouples consuming code from implementation. On Linux, that state is hidden deep inside libc library. You can replace the complete implementation of that state, or even eliminate it if you want so, the consuming code will run fine as long as malloc/free API stays stable. Quite often, code that uses explicit state becomes dependent on the particular implementation of that state. Much less likely to happen when the state is hidden behind some API, OOP or not doesn’t matter.
- jstimpfle 8y agoAbsolutely should you wrap malloc in any moderately sized project! But clearly that's not OOP. We need to differentiate between abstract data types (method dispatch, which I would normally count as OOP) and just making functions to "reuse" code. In the latter case, there is only one possible implementation, and the implementation can expose as much or as little implementation details as is appropriate -- and in general, there won't be any abstraction leaks.
- Const-me 8y ago> and in general, there won't be any abstraction leaks. For malloc, address space fragmentation is a common issue for 32-bit processes. Multithreading issues are common, when 2 threads access same cache line, it’s even slower than accessing main RAM. Randomized nature of the allocator causes performance problems. Every time people bother writing custom allocators, they do that because malloc/free abstraction was not good enough for them, i.e. has leaked. All abstractions leak, OOP or not.