Friday, September 04, 2015

How JetBrains Lost Years of Customer Loyalty in Just a Few Hours

NOTE: This post was originally written after JetBrains announced a controversial new licensing model. Many people spoke out about it. The next day, they followed up to say that they were listening to the feedback, and two weeks later made a final post with significant refinements from their original announcement. But the content in this post predated either of those follow-ups. Keep that in mind when reading.


Yesterday's big news, at least for many developers, is that JetBrains - maker of popular tools like IntelliJ and ReSharper - is moving to a software-as-a-service subscription model for their products.

Previously, buying a JetBrains product got you a perpetual license and a year of upgrades. Once the license expired, any software you had received under that license would continue to work, but you would need to buy another license to get further upgrades. It was a simple model that worked just fine for many people, and most customers upgraded every year.

Starting November 2, though, that all stops. After that date, JetBrains will no longer sell these perpetual licenses. Instead, you can rent access to their software on a month-by-month basis.

And there was much raging.

Now, don't get me wrong. A subscription model has apparently been a common request, and some of the feedback to the announcement has been positive. This arrangement is especially good for consulting shops that do one project in C# and the next project in Java. Rather than committing to a year of use, they can choose to only pay for what they need in any given month.

But that's just one type of customer. There are also plenty of single-platform shops. I know a lot of people who just need ReSharper or IntelliJ. These customers will probably notice no big difference - they will renew for a year at a time, and probably get a small discount as well.

This all sounds great! What's the problem?

The first change, and probably the biggest, is that the software will apparently stop working when you stop paying for your subscription. That's probably going to impact indie developers the most. For a developer with an unstable income, it might be perfectly fine to stay on an older version of the software until they've stashed enough cash to afford the upgrade. That will no longer work. But it's not just indie developers. I've seen companies who forget to renew their licenses promptly or who have long and convoluted processes to approve the expenditure. I guess, under the new model, development grinds to a halt until the purchase goes through.

Another controversial aspect is that the software will need to phone home. Now, JetBrains has given a gracious window - the software only has to dial the mothership once every 30 days. And customers in an internet-restricted environment will be able to install a license server inside their network to manage the license pool. This is not an uncommon practice for enterprise or specialized software. But it does create an interesting challenge. The licensing FAQ indicates that it's allowed for an employee to use their personal license at work; I've often taken advantage of this. But it doesn't look like the JetBrains license server supports personal licenses. For people in an internet-restricted environment, it looks like this perk is no longer available.

OK, so users lose some ability that they previously had, but the software is cheaper, right? Customers win a little and lose a little, so maybe it's a wash. Yeah, the software is cheaper... sort of. My last IntelliJ upgrade was $99 for the year. Under the new model, I'll only pay $89 for a year. Huzzah! Well, that's only applicable for users who already own IntelliJ. New users will pay $119 per year, which is a lot less than the old, introductory price of $199. But here's the deal: if I ever let my subscription lapse, it looks like I end up losing my grandfathered discount. And even then, the prices given are all listed as promotional prices that are only good until Jan 31, 2016. Is this a sign that the prices will jump in the near future? JetBrains certainly tried to promote this new licensing model by saying that it would make their software more affordable. It does make it cheaper, especially for new users (which is great!), but the situation for existing users is a little more murky. It's only cheaper for me if I keep renewing promptly. If I ever miss a renewal, my yearly costs jump by 30%.

But none of those details really explain why the internet got so upset. I think JetBrains miscalculated just how much people like the current licensing model. Sure, offering a subscription-oriented model makes sense for some kinds of customers. But there are many other customers for whom a subscription model is going to be worse. JetBrains indicated that this change is being made primarily to provide a better service to their customers. The feedback that they got today is that many customers don't see the new scheme as an improvement. Now, JetBrains has said (update 3) that they would take the feedback under consideration, which is definitely a good sign.

It's always awkward when a company says "this is good for our customers" and the customers respond with "no it's not". We saw this a few years ago with Adobe. In that case, it was completely clear that they didn't care what their customers wanted. They had decided on a course of action and nothing could stop that train. But I don't think anybody was surprised to see Adobe go in that direction. People liked Adobe's products, but I don't know that anybody really liked Adobe as a company. JetBrains was different. They built a loyal customer base on quality software and reasonable policies. JetBrains products had become the examples people used when saying "you know, open-source is great and all, but I'm happy to pay for quality software". When I read some of the responses to yesterday's announcement, I get the impression that existing customers feel a sense of betrayal. They're confronted with the idea that maybe JetBrains is no different from Adobe. Maybe all the goodwill that they felt for this company was misplaced.

Ultimately, JetBrains's response to this kerfluffle will show the underlying motivation behind this change. Will they listen to the feedback and truly offer licensing options that keep everybody happy? Or will they double-down on the software-as-a-service model, in the hopes that the controversy will just blow over?

Of course, listing the problems isn't super useful. If anybody from JetBrains reads this, I do have some suggestions for what you could do to appease the crowds:

  • Continue to offer perpetual licenses. I don't think people are bothered by you offering subscription licensing; indeed, some customers seem to prefer it. But for customers who are happy with the status quo, forcing them to switch and threatening them with software that could suddenly stop working, it's a really hard pill to swallow.
  • Or... require that corporate licenses be subscription-based, but continue to offer perpetual, personal licenses. I'm guessing that most of the people upset with this change are people who are currently using personal licenses. These are probably your most loyal, and also most vocal, customers. These are the kinds of people that get your products into an enterprise environment. At least keep them happy.
  • Take another look at your pricing. You're asking users to replace perfectly functional software with software with a coin slot; if you stop feeding money into the meter, the software stops working. You have to give those users something in return. If you did something drastic - like cutting those prices in half - people might be far more willing to accept this software-as-a-service model.
  • Offering lower introductory prices is great! But you don't need to fundamentally change your pricing model to do that. You've offered sales before - I got my initial ReSharper and IntelliJ licenses during your end-of-the-world sale back in 2012. If you want to attract new users, you could just, you know, lower your buy-in price. Heck, you could even raise your renewal prices by 10%. I suspect that such a change wouldn't have even raised an eyebrow.
  • (Late edit after reading more comments) As a reward for subscribing for a year or more at a time, issue perpetual licenses for products released during that time. If I subscribe for a month and then let my subscription lapse, my software stops working. But if I subscribe for a year and THEN let my subscription lapse, any software released during the window continues to work. This creates a situation where JetBrains keeps making money, but customers aren't punished for letting their subscription lapse.

I was a huge Eclipse fan back in 2010, but a friend convinced me to switch to IntelliJ and I've been a loyal user since. I'm not writing as an outside observer, but as a concerned customer. Now, JetBrains doesn't really care about my business; I'm guessing that I pay for something like 4 of their developer hours per year. But people like my friend, and now me, are vital to JetBrains growing their business. I pushed and pushed to get ReSharper installed on all my coworker's machines; that ended up being something like 10 corporate licenses, which pays for a lot more development time. JetBrains got to where they are today by building a very loyal fanbase. I hope they realize that alienating that fanbase could tear them back down.

Thursday, June 18, 2015

A quick overview of what WebAssembly is and what it is not (yet)

There's been a lot of buzz today about WebAssembly, and that's completely understandable. A bytecode form of Javascript has been on the minds of many web developers for a long time. But the online commentary seems to have been based more on hopes than on released information. I hope to clear up some of that confusion. Note that I'm not involved in the project at all, so some of this information is likely incorrect; feel free to leave a comment if I've gotten something wrong.

WebAssembly isn't a spec yet. It's not even a draft spec. It's an idea and a proof-of-concept. So far, that's all that's in the public. But it has a lot of promise.

The WebAssembly roadmap essentially spells out three phases:

  1. A minimum viable product (MVP) that is roughly analogous to asm.js, specifically targeting C/C++ as the source language
  2. Additional features, such as threads, SIMD, and proper exception handling, all still with a focus on C/C++
  3. "Everything else", including features meant to support more languages. One such feature is access from WebAssembly code to objects on the garbage-collected Javascript heap. This could enable WebAssembly code to access the DOM and web APIs (which would not be supported in either of the previous iterations). This phase also contains support for things like large heaps, coroutines, tail-call optimization, mmapping files, etc. If and when we get to this phase, I would expect it to get further split.

There will also be a polyfill, since browsers will not initially support WebAssembly. In fact, there is already a polyfill, but it's more of a proof-of-concept than an actual implementation. There is no WebAssembly spec yet; this polyfill is merely to test the viability of a binary-encoded AST.

Initially, it's not wrong to think of WebAssembly as binary-encoded asm.js, though that will change over time. It may eventually grow to the point that it could replace Javascript, but in its first two incarnations, you will still need some JS code (to interact with the DOM and with web APIs). A likely use case is to compile low-level, algorithmic code (think image processing, compression, or encryption code) to WebAssembly, but to still write the bulk of your application in plain JS. If you're not familiar with asm.js, it might make sense to look into it; many of the same limitations of that environment also appear to apply to the first incarnation of WebAssembly.

However, even at this early stage, WebAssembly does specify some features that even asm.js doesn't provide. In particular, it talks about 64-bit integer operations, which asm.js can't natively provide. The current polyfill POC doesn't seem to support them, and they may choose for the polyfill to always implement them as floating-point operations, but an actual runtime would need to provide proper support for int64 (and other sizes as well, like int8 and int16).

WebAssembly only deals with the binary format and runtime environment; it doesn't provide any new APIs for dealing with the DOM or the network or anything like that. It may eventually provide alternatives to other web APIs (WebWorkers would be an obvious one, when WebAssembly eventually adds threading support).

The current plan for WebAssembly is to not use bytecode in the same way as JVM or CLR use bytecode. Those VMs represent their bytecode as an instruction stream, similar to instructions for non-virtual machines. WebAssembly, on the other hand, appears to be going more for a "serialized abstract syntax tree" form. The claim seems to be that this can be compressed more efficiently than can be achieved using general-purpose compression routines, and that doesn't seem too crazy. This distinction isn't important for web app developers, though it could mean that "disassembled" WebAssembly is easier to grok than disassembled JVM bytecode.

WebAssembly will have defined binary AND text forms. So, while you won't be able to curl a WebAssembly file and immediately understand it, there will be tooling to convert from the binary form to the text form (which will certainly eventually be built into browsers). It seems likely that the bytecode semantics will make concessions to retain some degree of human readability when converted to text form.

I really like this quote from Peter Kasting in the comments on Ars. Like, seriously, if you have an Ars account, go give him some fake internet points.

Notably, the people working on WebAssembly are the PNaCl (Google) and asm.js (Mozilla) teams. In some sense this can be considered the followon effort to those projects, meant to combine the best attributes from each, and in a way that can be agreed on by all browser vendors.

This is exactly how the system is supposed to work: individual teams try to advance the state of the art, and eventually all those lessons learned are incorporated into a new and better system. See e.g. SPDY -> HTTP2. WebAssembly draws on both the past work and the experience of all those involved, and wouldn't be what it is without them.

I say this partly so the sorts of people who have bemoaned "non-standard" vendor efforts in the past may have reason to pause next time they feel the urge to do so. No one wants a balkanized world forever, but that vendor-specific effort may gain the sort of real-world experience necessary to come back and design a great cross-vendor solution afterwards. All four major browser vendors have taken flak for this sort of thing in the past, and in my mind often unfairly so.

All told, it looks like this is just the first, small step. I'm a little surprised that they went public so early. Either they really plan to develop it in the open, or the tech media got wind of it and blew it well out of proportion. Whatever the case, this looks like it will be a large undertaking. It's very promising that everybody seems to be at the table. And given that the browser vendors appear to want to actively develop their products, we might actually be able to use this within a couple of years.

What a time to be alive!

Tuesday, June 16, 2015

The joys of parsing a toy programming language

In my personal time, I've been playing at building a toy programming language. Progress has been slow, but things are finally starting to come together. Last night, I ended up finding an interesting bug, and I wanted to document it here.

The syntax of my language isn't super complicated. Here's an example of a function:

def foo(a, b) = a + b

Because this language is a so-called modern language, it has to support lambdas as well. Here's an example:

val bar = (a, b) => a + b

You can call functions and lambdas using the same syntax:

foo(5, 7)
bar(5, 7)

Naturally, since lambdas are first-class, we need to support more complicated call syntax as well. In particular, this should be allowed:

((a, b) => a + b)(5, 7)

In order to support that, here's the section of the grammar that describes function calls (you can imagine the rest of the grammer):

FnCall    :=  Callable lparen Args rParen
Callable  :=  identifier
          :=  lParen Expression rParen
Args      :=  identifier MoreArgs
          :=  epsilon
MoreArgs  :=  comma identifier MoreArgs
          :=  epsilon

My compiler was able to successfully compile and run this extremely simple program:

val bar = (a, b) => a + b
bar(5, 7)

But imagine my surprise when it failed to compile this program:

val bar = (a, b) => a + b
(bar)(5, 7)

Looking at the parse tree, it was obvious what had happened. It turns out that my grammar was ambiguous, and the parser had chosen this interpretation:

val bar = (a, b) => a + (b(bar))(5, 7)

Rather than parsing this as a definition and a corresponding expression, it instead parsed it as a single definition. It assumed that b was a function of one parameter, which returned a function of two parameters. Even this simpler grammar has the same problem:

FnCall    :=  identifier lparen Args rParen
Args      :=  identifier MoreArgs
          :=  epsilon
MoreArgs  :=  comma identifier MoreArgs
          :=  epsilon

Given this program:

val bar = (a, b) => a + b
(5 + 7) * 2

This could (and would) get parsed as:

val bar = (a, b) => a + b(5 + 7) * 2

(It's worth noting that, if I had been using a shift / reduce parser, this would have been detected as a S/R conflict. But I'm not using a S/R parser; I'm using the Scala parser combinator library, mostly because it was easy to get started and I haven't outgrown it yet.)

Looking at Scala, from which I stole a lot of my syntax, I found that newline handling is a bit complicated, though the rules are laid out plainly in section 1.2 of the Scala language spec. Essentially, a newline can be treated either as plain whitespace or as a statement terminator, depending on the context. Within a parenthesized expression, newlines are always treated as whitespace. Outside an expression, newlines are treated as statement terminators if they appear between a token that could end a statement and a token that could begin a statement. One one hand, having written a fair amount of Scala, the rules feel pretty natural and intuitive. However, you do end up with strange behavior at the edges, as the following example demonstrates:

(succ
  (5))   => 6

{succ
  (5)}   => 5

On the other hand, Haskell (with which I'm admittedly not very familiar) appears to use indentation level to determine expression grouping. That is, while this is valid:

main = putStrLn "hi"

and this is also valid:

main = putStrLn 
 "hi"

this is not:

main = putStrLn 
"hi"

Context is not considered, so unlike Scala, this still fails:

main = (putStrLn 
"hi")

Both of these approaches have merit. Haskell's approach is natural enough, though there are some unambiguous representations that it would reject. Scala's approach is more permissive, at the cost of more complicated implementation rules and more surprising behavior. I can't tell which is more appropriate for my language.

For now, I took neither approach. I realized that, as far as I know, I have but one case where expressions are ambiguous - when the parameters to a function appear on a separate line from the Callable itself. It was possible for me to simply require that the whitespace between the Callable and its parameters not include any newlines. So an identifier at the end of a line will NEVER be considered to be a function call. I don't think this will remain this way forever, but I think it will get me unstuck.