Intro

Elden ring is very popular, selling 12M copies in its first 3 weeks. But why? The graphics are poor compared to current gen offerings, there is little recorded dialog, a confused and often contradictory plot, and enemy AI with the intelligence of driveway gravel. Even features like lip sync are missing despite being common among RPGs for years. 

From Software, the creators of Elden Ring, are well know for their Dark Souls series. Even those who have never played a game from the series have likely heard of its notoriously non-adjustable difficulty. So is the difficulty of Elden Ring the reason for its popularity? Are players tired of easily completed games? 

No. Elden Ring’s ‘difficulty’ is the beginning of the reason for its success but it is not the end. 

Git Gud is a lie

Strictly speaking I would not say Elden Ring is difficult. I cleared the game and a fair number of side quests in just under 70hrs. In that time I never felt that game was difficult in the traditional sense. 

Typically when a skill is gained it represents an improvement within a specific domain. If I train to run a 10k I would also expect this training to help me run a 5k since both tasks involve running. Elden Ring is not like this. Its boss battles are won by memorizing the attack patterns of each specific boss. Mastering any given boss does not provide much insight into fighting the next boss. So you don’t really ‘git gud’ at Elden Ring instead you ‘git gud’ at each enemy in Elden Ring. If, however, you consider a game difficult if you die frequently then Elden Ring is a very difficult game. 

This pattern is true of other games as well with the Souls series being a prime example. The player is expected to die many times over as they encounter and learn how to react to each action an enemy can take. In a conventional game difficulty is scaled such that an average player can experience and adapt to new enemies without dying. Elden Ring players will die a lot and those not expecting this type of gameplay can easily become frustrated. And where do frustrated players go for help? What can they do to help short circuit this painstaking learning process?

The Ultimate FAQ

In the dark days before the internet, strategy guides allowed players to engage in optimal play without having to discover it through trial and error. GameFAQs replaced this model and then YouTube and Twitch replaced the FAQs. Now struggling players can turn to videos or even livestreams for advice. 

Easy games are less likely to send players running for help than difficult games. Therefore the most difficult games will generate the greatest demand for assistance. For the top 1% of players who master Elden Ring there is an incredible opportunity for content creation. The thirst for Elden Ring tutorials and guides is immense. Mass consumption of this content is detected by trending algorithms and promoted. The closed network test provided an excellent opportunity to get this cycle started. By the time Elden Ring went on sale it was already engaging its potential player base. 

Elden Ring’s vaguely worded patch notes are practically free money for these creators. Statements such as ‘Increased shield’s effectiveness’ are fertile ground for exploration.  Which shields? By how much? In what circumstances? Each patch spawns a fresh wave of player questions which is followed by content to satisfy them. 

The Shared Pain community

This content consumption cycle creates a community. Players who benefit from a tutorial leave comments. Whether these are gratitude for assistance or stories of how absurdly long a boss took to clear they build a community. This extends even to the game’s lore. Because so little information is given each player can create and share their own interpretation of the game while others cite opposing evidence in their own opinions. This is engagement, the ultimate goal of marketing, and it has been building around From Software for a while. 

And now they are reaping their rewards. 

Conclusion

I can’t tell if From Software planned for this to happen or not. If so then they have a genius working in marketing. If not, it doesn’t change their awesome balance sheet. 

I understand players wanting to build a community around their favorite games. However, I hope it does not become a blindly copied trend. I want to enjoy a game’s exploration and discovery without needing external assistance to complete it before the gameplay becomes dull. 

Unfortunately I don’t think many studios will be able to ignore the revenue Elden Ring has generated. Copycats are likely to soon follow. 

 

Suppose you asked someone what they did for a living and they said, “I am a hammer”. You might find this strange. You might also find it strange for them to say, “I am a table saw”. However it would be quite normal for them to answer saying, “I am a carpenter”. 
Far to often we confuse our proficiencies with the tools of our trade with our actual profession. This is not a good outlook. I use the tool not the other way around. I am not the programming language I am the software engineer. 
So why bring this topic up? Because anyone who believes they are defined by the skills they have in their tools will be limited by those tools. Remember this when you get the chance to learn something new.
We do not always get to pick the project we work on

Sometimes a company will halt all development on a project only to restart it at a later (sometimes much later) date. The reasons for this are many and varied but there are a few things to keep in mind which might help you if you ever have to restart an old project.

Find all of the resources involved with the old project. 

This would obviously include the source for the project. It also includes build scripts, tests, requirements docs, and any other support files.
In addition to the files which make up the project it is useful if you can find the systems which were involved in previous development such as build servers and developer workstations. Some parts of the project may not have made it into source control or it may have an unexpected prerequisite to build or run. These may be identified by examining the old build/dev boxes.
You may also try to track down any email chains involving the project. Even if all of the people involved in the original project are gone the company may have kept an archive of their inboxs in case they needed to be referenced for just this purpose.

Determine the state of the project

Once you have all the bits of the project together it is time to determine where the project was when it stopped. This will accomplish a couple of things. First it will help you and those who want to restart the project determine if the restart is actually worth the time. Second it will keep you from accidentally rewriting functionality which is already present but not immediately obvious. 

Setup or create a build process

If the project has a set of build scripts or some manner of build process it should be reinstated on a nightly or continuous integration build machine. If not then a process should be created.
If this is an internal tool then it should copy its binaries (or other end user useful bits) to a build repository. If this project has installers or some other more involved deployment system then these deliverables should be built as part of the build process and copied out to your build repository.
A build created on a dev workstation should only be used by a dev. Only those builds created by a build server (which should be labeling the builds) should be distributed, handed to QA, or shipped to customers. Adhereing to this rule will prevent one off and otherwise unrepeatable builds from entering the wild. 

Are there unit tests for any part of this project?

If you should be so lucky then these are the next items up to reinstate. Tests will help give you an idea of how the various parts of the project are supposed to work (or expected to break) as intended by those who wrote them. They can also catch fatal flaws you might introduce in the future due to a lack of knowledge of how a particular component is intended to be used.

Deploy a build for QA

Using the output of your now functioning build process deploy an instance of this project and the test it. Validate the current level of functionality of the project. If you have a bug tracking system which has records for this old project then check any obvious issues against any open defects.
The goal of this step is two fold. First is to ensure the project is still in sync with any requirements documents and bug tracking systems you may have. Second is to survey for yourself the true current state of the project with a build and deployment which you can now reproduce.
A quick note about build reproducibility. You should always ensure your build process can exactly recreate any arbitrary build which has come before. There are several insidious things which could prevent this ability with the simplest example being files not under source control (possibly some infrequently modified data files or libraries). Should a defect be introduced into files not under source control this defect will effect all new builds including those of older versions or on branches.

Validate the restart of the project

This point may arise earlier based on what you find along the way, if not then now is the time to consider whether restarting the project is worth it. 
It may turn out the project doesn’t actually do what project management thought it did. Or perhaps it does do what project management thought but is at a lower level of development than expected. A great number or severity of defects may also make the project a non starter. The project may have some big external dependency which is difficult to support or expensive to license. Even if none of the previous apply it may be difficult to add the new features project management wants to the project given the way it has been designed and built. 

The last and possibly most important point

If the objective of restarting an old project is to add some small feature to an old but operational deployment you may be tempted to just make the change to the source, build on your local system, and manually update the binaries on the production system. This is very bad idea. 
If you successfully pull off the update, then all is well. If, however, there are complications it will be very difficult to determine if the issue is caused directly by your update, indirectly through its interaction with other parts of the system, or if your update had nothing to do with the new issue at all. Additionally if you made a manual update to a system which is normally deployed via an installer you have now created a deployment which you may not be able to reproduce thereby making QA by another group virtually impossible. 

NOTE: this will generally pertain to C# but applies to any programming language which uses similar exception handling (for example Java). This article covers a few misconceptions about exceptions and is not intended as a complete reference. In depth study of this important programming aspect is highly recommended. 

Overview

The exceptions a method throws are as important as its parameters or return value. They are part of your contract with the user, even if you are not required to declare them as part of your method definitions (as you are in Java). Too often methods are completely written before any thought is given to the exceptions it should throw. This unfortunate behavior leads to a less than ideal interface for the user.
Many developers believe, and in many cases are taught, exceptions exist to handle rare or infrequent conditions encountered during execution. The truth is as developers we have no control over the circumstances our code is used in beyond the compiler enforcing the existence of our parameters. As such we can never say what the frequency of any use case may be. Thus we can never say if any given case is rare or common.

Usage

The correct use of exceptions is to convey our inability to keep our contract with the user as defined by our method signature. Let us consider the following method signature from the Int32 type:
public static int Parse(string s)
This method takes a string representation of an integer and returns its integer representation. Viewing the documentation reveals it throws the following exceptions:
ArgumentNullException – thrown when s is null.
FormatException – thrown when s cannot be converted to an int.
OverflowException – thrown when the representation of s is beyond the capacity of an Int32 to represent.
Each of these exceptions represents a condition which prevents the method from honoring its contract, returning an integer representation of the string parameter, to the user. There is a different exception for each semantically meaningful mode of failure. That is to say each exception represents a different reason the conversion failed giving the caller the ability to make a meaningful decision about how to proceed. The OverflowException can represent failure due to either a value above or below what can be represented by an Int32. As the user would not take different actions based on whether the value is too big or too small we have just one exception to represent both instead of an OverflowHighException and an OverflowLowException.

Example

Using these ideas let us design our own method. Let us suppose we have a type which represents a temperature probe. We might start by writing a class method with the signature:
public float GetTemperature()
[Note: normally we would use a property here but for demonstration purposes we will make this a method. Also, interestingly enough, the CLR doesn’t know about properties so C# converts them into methods during compilation]
What exceptions should this method throw? Anything which violates our contract with the user should be represented by an exception. In this case any condition which would prevent us from returning a temperature will need an exception. We could start with the following:
TemperatureProbeNotAvailableException – thrown if our connection to the probe is lost.
TemperatureOffScaleException – thrown if the probe provides a value beyond what its specification states it can accurately measure. 
Previously when considering the Parse method we said there was no need to have multiple exceptions to represent the high and low possibilities for the OverflowException. Here we also have a single exception to represent an out of range condition. Considering further is there any action our users might wish to take based on whether the temperature probe is off scale high vs low? In this case there is.
If the probe were attached to a heating or cooling system knowing which direction the temperature is off scale would be very meaningful information. For a more awesome example let us say this was a very high temperature probe in the exhaust of a gas turbine engine. Many such probes do not operate correctly until they reach a minimum temperature well above normal ambient. An off scale low condition might be normal while the engine was offline or idle but an off scale high might indicate the engine was running beyond specification and should be throttled down.
Since the direction of the off scale condition is potentially meaningful to the user we should provide exceptions for these conditions.
public class TemperatureOffScaleHighException : TemperatureOffScaleException
public class TemperatureOffScaleLowException : TemperatureOffScaleException
Though the off scale direction of the temperature probe could be useful to the user this might not always be the case. By extending the high and low exceptions from the TemperatureOffScaleException we give the user a choice  If they do not care about the direction of the off scale condition they can choose to catch the base TemperatureOffScaleException. Otherwise if they have code to handle the high and low conditions differently they may catch these derived exceptions in separate catch blocks and deal with them as they see fit.

Aren’t Exceptions Time Expensive?

Depending on the environment the throwing of exceptions can be time expensive, especially if they repeatedly do so in tight loops. The previously mentioned Int32 type addresses this with the TryParse method. 

public static bool TryParse(string s, out int result)

This method does not throw a FormatException. Instead if will return a boolean indicating if the conversion was successful and if so it will provide the converted value via the result out parameter. By using a boolean instead of an exception this call avoids the cost associated with handling an exception.

Does this method keep its contract with its user? Yes it does.
Whereas the Parse method implies by its signature it will return a converted value TryParse implies it will attempt a conversion. This distinction in naming is important in creating an API your users will be able to easily understand.
A simple look through the FCL will demonstrate the occurrence of TryX is very limited. While the TryX methods seem to match the X method in capacity and excel it in speed it fails in code simplicity. The code necessary to support the TryX method is more cumbersome than the a simple X method.

Conclusion

There are various opinions about the role of exceptions and how they are used. What I would like to convey here is exceptions are an important yet overlooked aspect of development in environments which support them. A more in depth study than what is presented here is recommended though simply considering them at the outset of development instead of as a postscript will help in API design.