[HN Gopher] The case of the critical section that let multiple t...
___________________________________________________________________
The case of the critical section that let multiple threads enter a
block of code
Author : luu
Score : 105 points
Date : 2025-03-23 08:14 UTC (14 hours ago)
(HTM) web link (devblogs.microsoft.com)
(TXT) w3m dump (devblogs.microsoft.com)
| davydm wrote:
| Or rather, "the case of a buggy lazy-init function which
| reinitialized the critical section every time"
| DamonHD wrote:
| No spoilers!
| akoboldfrying wrote:
| Loose typing strikes again.
|
| I understand the temptation of "Let's just use an int return
| type, that way later on we can easily let them indicate different
| flavours of error if we want". But then you leave yourself open
| to this.
|
| The full-BDSM approach is for every component Foo that works with
| callbacks like this to define its _own_ FooResult type with no
| automatic type conversions _to_ it. In C, enum suffices, though a
| determined idiot can just cast to override; using FooResult as
| defined below makes it harder to accidentally do the wrong thing:
| enum FooResultImpl { FOO_SUCCESS_IMPL, FOO_FAILURE_IMPL };
| struct FooResult { enum FooResultImpl
| USE_A_CONVERSION_FUNCTION_INSTEAD_OF_ACCESSING_THIS_DIRECTLY;
| } FOO_SUCCESS = { FOO_SUCCESS_IMPL }, FOO_FAILURE = {
| FOO_FAILURE_IMPL };
|
| (Yes, it's still possible to get an erroneous value in there at
| initialisation time without having to type out "USE_A_CONV..." --
| but this is C, you can only do so much. You can't even prevent a
| FooResult variable from containing uninitialised garbage...)
| emn13 wrote:
| Is the advantage over an enum not kind of small? We're seeing
| bugs here because people tried to do the right thing but the
| tooling has absolutely no way of helping anybody to do that.
| Simply preventing accidental mistakes would prevent these.
| Adding complexity to make it harder (though never impossible)
| for consumers to misuse the API in a complex way seems like
| it's potentially going to far.
|
| Then again, it's been years since I used this kind of C, so
| maybe my instincts are rusty here (no rust-pun intended!)
| akoboldfrying wrote:
| You're right, this may be overkill. OTOH, casting between
| integer types in C can (unfortunately) feel like clicking
| away confirmation dialog boxes -- something too readily done
| without full understanding ("Oh, it's always just 1 or 0, of
| course it will fit in the target type [so no need to think
| further]").
|
| While it's annoying boilerplate for the Foo component to have
| to write, I don't think clients of Foo see much additional
| complexity. They can still write "return FOO_SUCCESS;", etc.
| magicalhippo wrote:
| I kinda like the way Boost did error_code[1], which got
| incorporated into C++11[2].
|
| Essentially you got a generic error_code struct which has two
| members, an int to hold a given error value or zero if there's
| no error, and a reference to an error category which helps
| interpret the error value.
|
| Effectively the error category is an interface, so in C terms
| it would be a reference to an error_category struct filled with
| function pointers.
|
| There's then some machinery which allows you to compare
| specific error codes to generic error conditions like "file not
| found", abstracting away the specifics of the error code.
|
| It's not problem free[3], but I've used this pattern in
| languages like Pascal and felt it worked well for me.
|
| [1]:
| https://www.boost.org/doc/libs/1_82_0/libs/system/doc/html/s...
|
| [2]: https://en.cppreference.com/w/cpp/header/system_error
|
| [3]: https://akrzemi1.wordpress.com/2017/10/14/error-codes-
| some-c...
| alfiedotwtf wrote:
| Yes, a "Sum" type in Type Theory
| tialaramex wrote:
| I think I don't understand why they're making a critical section
| at all.
|
| The end goal is to initialize something no more than once, right?
| But the technology they're using (wrongly, but it did exist and
| they were clearly aware of it) to make a critical section _does_
| initialize a thing exactly once.
|
| I also don't understand the use of SRWLock here, or rather, I
| sort of do but it's a hole in Microsoft's technology stack.
| SRWLock is a complicated (and as it turns out, buggy, but that's
| not relevant here) tool, but all we want here is a mutex, so we
| don't actually need SRWLock except that Microsoft doesn't provide
| the simple mutex, so this really is what you'd have written at
| the time+ - conjure into existence the over-complicated SRWLock
| and just don't use most of its functionality.
|
| + Today you should do the same trick as a Linux futex although
| you spell it differently in Windows. It's also optionally
| smaller, which is nice for this sort of job, a futex costs 4
| bytes but in Windows we can spend just one byte.
| dgellow wrote:
| What makes SRWLock buggy or over complicated?
| Wumpnot wrote:
| In some rare cases you can get an exclusive lock when you
| asked for a shared lock, most code won't care and will still
| work correctly, but sometimes people do weird shit that they
| probably should have used a different construct for, and run
| into this.
| colonwqbang wrote:
| The point of an RW lock is that it supports multiple
| concurrent readers. Why is it weird to expect a non
| exclusive lock operation to be non exclusive?
| tialaramex wrote:
| The idea of SRWLock is, as its name might suggest, it's a
| Simple Reader Writer Lock.
|
| This makes it in theory excellent for implementing this
| precise feature which you will see in for example C++ (the
| MSVC implementation is, in fact, just an SRWLock) or Rust
|
| So immediately it's over-complicated for a mutex because we
| don't need it to handle multiple readers, we're never taking
| its shareable reader lock, we (as you see in Raymond's
| example code) just rely entirely on the exclusive lock which
| is about 10% of its function.
|
| SRWLock is the size of a pointer - because of course in fact
| it is a pointer, to a lazily allocated structure - with some
| flag bits squirrelled away at the bottom of the pointer.
|
| The exciting bug is that at some point whoever was
| implementing this for Windows screwed up while implementing
| the (necessary for performance) unfair lock stealing
| strategy.
|
| Brief aside about "lock stealing": If I want to take a lock,
| and I notice that somebody else just released that lock, I
| can just take it and we're done - this is unfair because
| there may be a queue of waiters, and I cut in ahead of them,
| but it has better performance. This is a simple and common
| feature for a mutex - fair locks are _sometimes_ useful but
| definitely should not be the default.
|
| Now, in SRWLock the lock stealing code doesn't remember
| whether you're trying to take the shared reader lock or the
| exclusive writer lock. So, it just always steals the
| exclusive lock - even when that's not what you wanted. As a
| result in code relying on SRWLock if you take the reader lock
| and then wait on somebody else to also get that lock (which
| they should be able to because it's a shared lock) this
| _sometimes_ deadlocks. Oops.
| neerajsi wrote:
| I've worked in and around srwlock for a long time. I don't
| think it's all that reasonable to think you should get
| "shared starve exclusive" by default, which is what you're
| asking for. We used to demand that kind of behavior in some
| windows filesystem code and it's always been a design
| mistake.
| LegionMammal978 wrote:
| I don't think SharedStarveExclusive is what GP is talking
| about? From the description, it sounds like currently, a
| shared acquire immediately after an exclusive release
| will get silently 'upgraded' to an exclusive acquire in
| some cases (and remain exclusive until released), which
| can easily cause issues. And it can't even be detected or
| worked around, since the SRWLock functions don't return
| any information about the status of the lock.
|
| It looks like the Rust standard library migrated away
| from SRWLOCK on account of this issue [0].
|
| [0] https://github.com/rust-lang/rust/issues/121949
| Wumpnot wrote:
| SRWLock perf is slightly better than Window WaitOnAddress
| stuff, and works on older versions of windows.
| tialaramex wrote:
| I find this unlikely. Do have some real world evidence for
| this? Microbenchmarks are not at all useful for this stuff in
| my experience. For speed: In the uncontended case, which is
| what we mostly care about because if you're contended it's
| game over for speed, they're both a single atomic CAS, so
| that's no difference.
|
| In terms of size, the pointer is bigger than you'd want -
| it's no pthread_mutex or the analogous Windows data structure
| where it's a multi cache line disaster of a data structure -
| but it's clearly worse than a futex or WaitOnAddress
| solution.
| Wumpnot wrote:
| A few years ago I compared them, it was not a
| microbenchmark, but a real application. There were a few
| million(almost entirely uncontended) exclusive locks being
| taken on startup, SRWLock was consistently faster, though
| the difference was not large.
| tialaramex wrote:
| Thanks, I wonder what that's all about.
| colanderman wrote:
| They're calling `(*Callback)(g_myProvider, Context)` in the
| context of the critical section. So presumably that's also
| important to run exclusively.
|
| (The tracing handler getting registered twice is just a
| canary.)
| anarazel wrote:
| > Today you should do the same trick as a Linux futex although
| you spell it differently in Windows. It's also optionally
| smaller, which is nice for this sort of job, a futex costs 4
| bytes but in Windows we can spend just one byte.
|
| I don't really care about narrower futexes, what I do wish
| Linux had was 8 byte futexes. It's at times hard to cram enough
| state into 4 bytes for more complicated things.
|
| E.g. for postgres' buffer state it'd be very useful to be able
| to have buffer state bits, pin count, shared lockers and the
| head of the lock wait queue in one futex "protectable" unit.
|
| There are also ABA style issues that would be easier to defend
| against with a wider futex.
| valicord wrote:
| > The end goal is to initialize something no more than once,
| right?
|
| Took me a while to understand as well. They unregister the
| handler at the end of the critical section, so the requirement
| is not "no more then once ever", it's "no more than once at the
| same time".
| canucker2016 wrote:
| > The end goal is to initialize something no more than once,
| right? But the technology they're using (wrongly, but it did
| exist and they were clearly aware of it) to make a critical
| section does initialize a thing exactly once.
|
| FTA the InitializeCriticalSectionOnDemand function calls
| RtlRunOnceExecuteOnce to perform the bookkeeping work of
| running a function once. RtlRunOnceExecuteOnce is passed a
| function pointer to do the actual grunt work of running once,
| in this case, initializing a critical section.
|
| The problem is that RtlRunOnceExecuteOnce EXPECTS the function
| pointer to return non-zero for success and zero for failure.
|
| InitializeCriticalSectionOnce ALWAYS returns STATUS_SUCCESS (==
| 0) because it CAN'T FAIL because InitializeCriticalSection
| can't fail. So InitializeCriticalSectionOnce is telling
| RtlRunOnceExecuteOnce that it failed when it actually
| succeeded.
|
| NT kernel devs deal mostly with APIs that return NTSTATUS
| values, where 0 (== STATUS_SUCCESS) is success and non-zero is
| a failure.
|
| The problem comes when another thread calls the same code. The
| RtlRunOnceExecuteOnce API believes that the underlying code
| failed before, so it tries initializing again, initializing the
| same critical section again (even if the crit-sect is held by
| another thread currently).
|
| In goes another thread since the critical section has just been
| initialized and you've got multiple threads in the same
| 'protected' code now. Might as well not have a crit-sect.
| hyperhello wrote:
| It looks Windows is lousy with callbacks and APIs that put the
| burden of understanding everything on the user, and some of
| Windows uses 0 to mean success, and some of Windows doesn't.
| colanderman wrote:
| > some of Windows uses 0 to mean success, and some of Windows
| doesn't.
|
| This is unfortunately true of Unixes as well.
| jstimpfle wrote:
| Unix APIs return -1 on error pretty consistently. The error
| code can then be read from the errno thread local variable.
| In the Linux kernel internal APIs (and probably others),
| -errno is returned directly (no errno mess), which is still
| negative.
|
| The one "Unix" API I know that returns > 0 on error is
| pthread, which returns +errno directly (but still 0 on
| success).
|
| Which APIs return 0 on error? I can't think of any.
| colanderman wrote:
| `malloc(3)`. Many of the functions in string.h.
| jstimpfle wrote:
| Yeah there are a couple of (3) functions (i.e. not
| syscall interfaces) that return pointers. It's very
| common for those to return NULL on error, hardly a
| surprise. It will also blow up with a segfault should you
| forget to check success.
| umanwizard wrote:
| > It will also blow up with a segfault should you forget
| to check success.
|
| My guess is this is _usually_ true in practice, but in C
| and C++ dereferencing a null pointer is UB so you really
| can 't assume that.
| jstimpfle wrote:
| You shouldn't intentionally rely on a segfault as a means
| of terminating the program -- do sth. like `exit(1)`
| instead.
|
| What I'm saying is there isn't an ergonomic issue with
| NULL error return APIs. You might prefer algebraic error
| types (in other languages). I'm not even sure I do. NULL
| return APIs are clean and simple. If you do
| (accidentally) forget to check for NULL return code, you
| will for sure get a segfault on first access in practice
| (not on embedded platforms maybe). The compiler can't
| remove any following code unless it can prove only NULL
| will ever be returned. Don't fall trap to UB FUD.
| umanwizard wrote:
| > The compiler can't remove any following code unless it
| can prove only NULL will ever be returned.
|
| It can reorder code, though. For example, it can change
| int *x = malloc(sizeof(int)); *x = 42; if (x)
| launch_the_nukes();
|
| to int *x = malloc(sizeof(int));
| launch_the_nukes(); *x = 42;
| trebligdivad wrote:
| I've come across lots of C code which mixes whether it's
| using -errno, or errno and life all gets messy. And then
| there's the places where you're returning a pointer not an
| errno at all. (Which the kernel tries to solve with errptr
| I think - but there seems to be plenty of places fixing
| that) And then there's the way that using -errno ends up
| with ssize_t (signed that is) which also confuses loads of
| things.
| jstimpfle wrote:
| There are a lot of small details to know in order to use
| Windows APIs correctly. Which is stressful. On the other hand,
| comparing with Unix, the APIs are much much wider and are
| rarely if ever deprecated.
|
| Unix APIs are a couple of syscalls and library functions. If
| you want to achieve more (like video, audio...) you have to
| resort to "optional" libraries that may have bad APIs too, but
| they aren't core Unix and can be replaced by something that is
| hopefully better.
| wbl wrote:
| 50 years and they can't even spell create with an e at the
| end. That's some good backwards compat.
| muststopmyths wrote:
| It actually makes sense, but only to old Windows vets.
|
| The underlying NT (microkernel) api (as found in used-to-be-
| undocumented NTDLL) returns _STATUS_XXX_ from functions. With
| _STATUS_SUCCESS_ being 0. This is still consistent as far as I
| know.
|
| Win32 API, technically being a "subsystem" on top of NT,
| exposed to the user whatever convention its users were used to.
| Coming from Windows 3.1, it used _BOOL_ (int actually) where
| _FALSE_ was 0 and _TRUE_ was 1, in a (mostly) consistent manner
| for success /failure.
|
| An additional wrinkle may be APIs that return a _HANDLE_ (or
| address for memory allocators) or _NULL_ , which again if you
| treat as ints return non-zero for success and 0 for failure.
|
| Posix Subsystem, on the other hand, could return 0s and non-
| zeros as desired.
|
| So, if you knew Windows and thought about the API you were
| calling it made sense if you thought in terms of the types
| defined (BOOL, HANDLE, etc. for Win32) instead of 0 and non-
| zero.
|
| No idea if it this is still consistent over the surface of the
| entire exposed API set now. So much accretion of cruft over the
| years under the eyes of an uninterested leadership has led to a
| lot of fragmentation and hence deterioration in the developer
| experience.
| canucker2016 wrote:
| That's the thing about development with more than one developer
| designing APIs.
|
| Looks like the RtlRunOnceExecuteOnce API and structures
| appeared in Windows Vista.
|
| So one would have to do some code archaeology and dig into the
| Windows Git repo to look at the commits for
| RtlRunOnceExecuteOnce and see what the typical use cases were.
| For some reason, returning 0 for failure made sense. Given the
| pervasiveness of NTSTATUS, I have no idea what made them choose
| a non-NTSTATUS return value. Maybe there was another callback-
| type API which did something similar?
|
| If one dev thought that RtlRunOnceExecuteOnce required
| NTSTATUS, then I'm sure other devs will have thought the same
| thing so I'd expect more such bugs in the current codebase and
| in the future. That's why Microsoft static code analyzers
| (PREfix, PREfast) would flag such footguns.
| userbinator wrote:
| _some of Windows uses 0 to mean success, and some of Windows
| doesn 't._
|
| In cases where there is only one successful result, 0 makes
| sense. Then nonzero can be a range of error codes. In cases
| where there is multiple successful results and only one
| failure, 0 means failure; malloc() is the stereotypical example
| of this.
| robmccoll wrote:
| Looking at Microsoft's C code makes my eyes hurt. I don't know if
| it's the style (bracket placement, no new lines), naming
| conventions, typedeffing away pointers, or what, but it just
| doesn't read easily to me.
| pavlov wrote:
| The combination of all-caps type names and Hungarian notation
| for variables ("ppszOutStr") makes it feel like you're having a
| conversation with the vampire from the 2024 Nosferatu remake.
|
| He's kind of yelling slowly and kind of talking in some East
| European language, and yet you can kind of understand what he's
| saying.
|
| (And if I ever have to open an MFC codebase again, I'm going to
| be thinking of how Nosferatu springs up naked and rotting from
| his coffin)
| tialaramex wrote:
| Notably it's _Systems Hungarian_ which makes no sense
| whatsoever. This notation is a way to mention the _kind_ of a
| variable in languages which don 't directly express that. It
| starts in BCPL which doesn't have types, so if boop is a
| boolean and blip is a counter we need to annotate the name of
| the variable as the compiler doesn't see any reason you
| shouldn't use boop as a counter and blip as a boolean, so we
| maybe call them bBoop and cBlip or whatever.
|
| Now for the team writing say Excel, they have a typed
| language so they don't need to distinguish booleans from
| counters, but their language doesn't have or encourage
| distinct Row and Column types, those are both just integers,
| so "Apps Hungarian" uses the name to annotate variables with
| such information, clInsert is the column while rwInsert is
| the row, if I am reviewing code which is to inspect columns
| and it checks clFooC, clBar and rwBaz well why is it using a
| row number for a column? That warrants closer inspection.
|
| Unfortunately, this practice was divorced from its rationale
| and infected teams at Microsoft who _had_ a typed language
| and didn 't have kind information beyond that, but felt the
| need to use this notation anyway, producing "Systems
| Hungarian" where we mark out pFoo (it's a pointer named foo),
| and lBar (it's a long integer named bar). This is very silly,
| but at this point it has infested an entire division.
| canucker2016 wrote:
| Great description of the nuance between Systems Hungarian
| and Apps Hungarian.
|
| One further nuance is that return types of APIs also affect
| the naming of the APIs in Apps Hungarian. Boolean return
| values are typically called f for flag. An API that returns
| a boolean would be called FApiFunction - the capital F
| (camel case is used) at the beginning of the API name
| indicates that the API returns a boolean. If you write code
| that stores the return value in a variable that doesn't
| begin with an 'f' then any code reader would see the
| mismatch.
|
| FTA, one problem is that the API returns a ULONG where 0 is
| failure and non-zero is success.
|
| But the developer thought that the API returned an NTSTATUS
| error code, where 0 (== STATUS_SUCCESS) is success and non-
| zero is failure. This is the opposite of the documented
| return value for the API in question.
|
| In Apps Hungarian, the naming of the API would be obvious
| that it didn't return an NTSTATUS variable vs a ULONG.
| Systems Hungarian lost that tidbit somewhere. So developers
| must be more diligent about knowing the return values of
| various APIS (but assuming NTSTATUS usually works, expect
| in this case).
|
| But Apps Hungarian rarely names variables with native type
| prefixes. Variables are more than just the native
| underlying type (ULONG, int). Variables have a purpose. The
| return type of strlen or Win32 equivalent is an int or for
| more modern-designed APIs, an unsigned int (since string
| lengths can't be negative). In Systems Hungarian, you'd
| name the variable ulen. But in Apps Hungarian, strlen
| returns an int representing the count of characters. Apps
| Hungarian uses 'ch' for character and 'c' to denote a
| count. So the variable used to store the return value from
| strlen would be cch. (And in Apps Hungarian, you'd actually
| alias strlen to CchStrlen so you'd know just looking at the
| API that it returns a 'cch', count of characters, and you'd
| need a CCH type to store the return value).
| bombela wrote:
| And if atop of ASCII "ch" you add UTF-8 bytes and unicode
| character, what's the naming convention?
| canucker2016 wrote:
| bytes are bytes, and in Apps Hungarian, it'd be 'b' type.
| So 'cb' would be a count of bytes.
|
| I'm not sure you could place utf characters 'atop' of
| 'ch', more alongside/as well.
|
| Win32 had support for wide-characters (UCS-2, I believe)
| which mapped to WCHAR/'wch'. They also support, in the
| windows.h headers, for compiling code to support both
| single-byte characters and wide characters using one code
| base via macros/typedefs with the TCHAR type et al. This
| resulted in code that used 'tch' as the underlying
| character type.
|
| So code that only wanted 16-bit Unicode would have
| variables like 'wch' and 'cwch'. Code that supported both
| single and wide characters would have 'tch' and 'ctch'.
|
| UTF support in Win32 appeared after I stopped Win32
| programming. Looking up a few Win32 UTF-related
| programming docs, MS seems to have shoved UTF-8 support
| under the old Win32 Ansi-character/single byte string
| APIs. I'd probably create some Utf-type related APIs that
| called down to the Win32-related APIs. But it would also
| depend on the UTF-8 API design (strlen doesn't map 1-1 to
| UTF chars anymore) - parsing UTF-8 characters/code points
| is different than single-byte character set. I'd guess
| there'd be 'uch' for a UTF-8 character, probably a 'ucp'
| for a UTF-8 character point, etc.
|
| [edit] You wouldn't just repurpose single-byte string
| code with 'cch' for UTF-8 since the semantics aren't the
| same (not even if you include double-byte character sets
| like Japanese), one UTF-8 character can be one or more
| bytes which typical one/double-character set string code
| doesn't deal with.
| canucker2016 wrote:
| as a reference, the reference to 'ppszOutStr' that a
| previous poster mentioned wouldn't be typical Apps
| Hungarian.
|
| 'sz' is a null-terminated string, so naming a variable
| 'szOutStr' is redundant since 'sz' automatically tells
| you it's a string. 'szOut' would be fine. The 'pp' at the
| front tells you it's a pointer to a pointer. You
| typically only use a 'psz' to refer to a string (I
| usually used 'sz' since most of the time, you're dealing
| with allocated memory and that means I had a pointer
| alread).
|
| But if you want to reallocate the string memory , you'd
| have to take the address of the pointer (&psz) which
| meant you'd have a pointer to a pointer to a string:
| 'ppsz = &psz;'
|
| When dealing with a 'ppsz', you'd know that you couldn't
| pass 'ppsz' directly to a function that handled 'psz'
| variables, you'd need to dereference ppsz (*ppsz) to get
| a 'psz' to pass to those functions. Useful when dealing
| with liberal C compilers, not as useful with stricter C++
| compilers.
| canucker2016 wrote:
| Here's a link to a doc by Charles Simonyi describing Apps
| Hungarian. see https://cgtweb1.tech.purdue.edu/courses/cg
| t456/Private/Readi...
|
| MSDN Library seems to have totally nuked any pre-2005-ish
| URLs and their search engine is just bad as well
| (archive.org seems to have gotten tons of 302s/301s when
| crawling for this article). So you'll have to make do
| with a copy of the MSDN Library article at purdue.edu.
| alfiedotwtf wrote:
| For all the people who say to me "yOu DoNt NeEd RuSt",
| you can have your tyre fire languages where you need to
| build SOCIAL ABSTRACTIONS on top of the language because
| your type system literally came from the 1970s.
|
| ... I don't know, I kind of like my compiler catching
| issues like these rather than having to do it by eye and
| hoping for the best
| AtlasBarfed wrote:
| Syntax coloring and click nav in IDEs made Hungarian
| totally obsolete, and ms also took it to an extreme.
|
| I used to have different hungarian prefix conventions for
| local, global, argument and instance vars.
|
| I actually still use them sometimes
| RicardoLuis0 wrote:
| it's worth noting as well that the windows API has been a
| thing since 1985, _before_ C was standardized, such old
| versions of C were incredibly untyped
| switch007 wrote:
| Every facet of Microsoft seems to lack style. Every
| official/affiliated blog is ugly and inconsistent. C# looks
| ugly to me. Windows is fugly. Their fonts are horrid. It's to
| be expected
| jdthedisciple wrote:
| Partial disagree: Yes their blogs may be ugly.
|
| But C# is extremely beautiful, you can read and write it like
| a novel.
|
| Segoe UI is also not the worst font. I give it a 7/10.
| switch007 wrote:
| C# in Visual Studio is ugly with Windows font rendering, to
| me
|
| I find the title case ugly, not a fan of semicolons, nor
| the new keyword. Guess I'm too used to Kotlin and Linux/Mac
| fonts
| mystified5016 wrote:
| Your complaint is not about C#. You're complaining about
| _fonts_ and saying C# itself is ugly.
| neonsunset wrote:
| You do realize which IDE or text editor to choose and
| which fonts to use with either is a choice?
| watt wrote:
| well, C# and TypeScript is thanks to Anders Hejlsberg who
| does have sense of style.
| userbinator wrote:
| _But C# is extremely beautiful_
|
| Seriously? It's basically Microsoft-flavoured Java, and has
| much of the same horrid verboseness and architecture-
| astronaut overcomplexity.
|
| K&R, early UNIX style, BSD is what I find "extremely
| beautiful".
| cedws wrote:
| Think Steve Jobs once said something to the effect of
| Microsoft having no taste. I think I'd agree. Inconsistent
| design language, inconsistent values, inconsistent vision.
| vijaybritto wrote:
| Yet they've had astounding success and is the most widely
| used operating system. Maybe usability and ease of access
| are far more important than beautiful design
| marcellus23 wrote:
| Design _is_ usability and ease of access.
| AtlasBarfed wrote:
| Shockingly, monopolies and entrenchment in software is
| SUPER EFFECTIVE.
|
| While MS isn't utterly technically bereft obviously, and
| they had some correct choices like backward compatibility
| and usability relative to imbeciles like IBM, just look
| at the botched execution of Win8, their security
| disasters, and generally superior products that got
| nudged out by beta-ware quality releases.
| d3nj4l wrote:
| You can succeed despite poor taste, but that doesn't make
| your taste any less poor.
| canucker2016 wrote:
| If you think their fonts are ugly now, you should go back and
| look at the pre-Windows 3.x fonts. But you also have to
| realize they were saddled with CGA/EGA as their target
| display adapters back then.
| raverbashing wrote:
| Hungarian notation had a purpose to it, but then they went and
| did it wrong accidentally (there's an old Joel on Sw article
| about it, but I'm not linking it) - and in the example this is
| all over the place - not to mention C macros ooofffff
|
| But yes the mix of that plus camel case, make things confusing
| McP wrote:
| https://www.joelonsoftware.com/2005/05/11/making-wrong-
| code-...
|
| Why not link it?
| AtlasBarfed wrote:
| Did fogbugz ever hit it big? Joel's blogs are legitimate
| gold and almost timeless (AIpocalypse tbd).
|
| But they just did bug tracking and other jira type stuff in
| MS land, right?
| quotemstr wrote:
| You get used to it after a while. The NT style has an austere
| beauty all its own too. Typedefing away pointers creates a
| uniform and regular syntax --- no reading declarations forwards
| then backwards then forwards again. The doc comment style
| encourages thoughtful architecture. Opaque handle types are
| common, which is good.
|
| https://computernewb.com/~lily/files/Documents/NTDesignWorkb...
|
| Nobody likes Hungarian though.
| robmccoll wrote:
| Typedeffing away pointers means I don't know when copying or
| passing something if it's by value or by reference.
| timewizard wrote:
| The case of the company that writes overly verbose code and
| decides to change the meaning of names and constants between
| different libraries that actually do the same things.
|
| Microsoft saw all the footguns available and decided to just
| incorporate every one they possibly could and invent new ones
| whenever possible. For extra hilarity they put all this in a
| monorepo for all the good that has /never/ apparently done for
| them.
| xyzzy9563 wrote:
| Just use strong typing and mutexes. This isn't rocket science.
| ddtaylor wrote:
| Microsoft take note that I read this article and everything
| Raymond Chen puts out under your company brand. I have zero
| interest in Windows as a platform and actively steer large
| customers away from it anytime it's discussed, since it has no
| value offering for most of us.
| psd1 wrote:
| YSK that someone has got into your account and posted dumb
| shit. Change your password.
| putzdown wrote:
| I wake up every morning and thank God I am not working on or near
| Microsoft code. There is nothing about this code or anything
| about this story that is in any way sensible or pleasing. Take a
| simple, well-solved problem. Forgot all prior solutions. Solve it
| badly, with bad systems and bad ideas. Write the code in the
| ugliest, most opaque, most brittle and fragile manner imaginable.
| Now sit back and enjoy the satisfaction of getting to debug and
| resolve problems that never should have happened in the first
| place. The miracle is that Microsoft, built as it is to such a
| degree on this kind of trashy thinking and trashy source, still
| makes its annual billions. That right there is the power of
| incumbents.
___________________________________________________________________
(page generated 2025-03-23 23:01 UTC)