The first few months of this year were an awesome ride. I accomplished some things that were hard to do for me, but also hat to take some hard decisions. Here, I want to write about a few things that happened so far, and how I plan to move on…
This is a mostly personal post. I hope you still find it interesting. Ping me on Twitter to give me feedback (link at the bottom)…
Accomplishment: A Book
I finished and self-published my second book this year. The first one happened by accient. Ok, not really by accident, but I did not really plan to write it.
The second book, Quick Glance At: Agile Anti-Patterns was a planned project.
I kind-of knew that I did not want to do it with a publisher, after a very stressful experience in the past. But after I recognized that self-publishing is not that hard, I decided to write a book about agile software development.
I started and wrote some chapters. And I did not really like what was emerging. Then I had the idea with the anti-patterns. So, I threw everything away and started over.
I poured a lot of effort and money into it (e.g. I hired a very talented illustrator). And in early 2018, I finished it. Have a look at it here (ebook and paperback available): Quick Glance At: Agile Anti-Patterns
Decision: Employee or Not?
One of the most interesting companies I know asked me to come to an interview. After that interview, I faced the toughest decision of this year (at least so far).
I never really considered full-time employment. I have been a freelance consultant and coach for over ten years now. And, had you asked me last year if I wanted to become and employee, I would probably have answered: “Not really, except maybe at [names of three or four interesting companies]”.
Don’t get me wrong: I think that many more companies do great work and work on interesting problems. But… I think, in many cases I can provide more value (and have a better chance of doing what I am good at) as an external consultant.
Anyways, when one of the very few companies invited me for an interview, I had to go. A short time later, I told them: For now, I want to stay independent.
This was a hard decision: They work on really interesting problems, have a great engineering culture and there would have been many people I could learn from.
But in the last few years I have tried to learn and improve a few things my clients usually need help with, and for now, I want to build on them.
I have tried to get good at coaching teams, teaching them technical practices like Test-Drive Development or refactoring, practicing pair-programming or mob-programming, dealing with legacy code and facilitating meetings and discussions. And, at least for now, I want to learn even more in those areas and help my clients to get better with that.
(Does agile coaching or technical coaching sound like something your team or organization might need? Let’s talk…)
Conferences, Meetups, …
I am speaking at a lot of conferences and meetups this year - At least compared to past years. While I really enjoy it, it’s also exhausting. And hard to coordinate with my family.
My topics this year are about software quality and it’s relationship to speed, cost and agility, agile anti-patterns, and also technical topics like React and Redux.
Do you want me to speak at your event? I would love to do that. Let’s talk… - Bonus points if you make sure that you have an enforcable Code of Conduct in place, don’t make me #paytospeak and if you at least try to create a diverse event.
I am, for the third time, co-organizing the Software Crafting and Testing (SoCraTes) Austria, together with Elisabeth Rosemann and Rene Pirringer. Ever after visiting the “original” SoCraTes Conference in Germany, I wanted to organize such an Event in Austria.
And I am very glad that Elisabeth and Rene (and some others who advise us) joined me, because without them, I could not do it.
And I am also glad that they share my passion about creating a diverse and safe event. We put some considerable energy into making this conference interesting and accessible for everyone. We serve vegan food, have an enforcable code of conduct, an accessible venue, and we offer student discounts (ask me) and a diversity tickets campaign. And we are working on getting better every year.
This year, things are looking good so far: We have already sold as many tickets as in the first year (2 years ago), and we hope the conference will be sold out for the first time (100 attendees). And we already have enough sponsors so we can make the conference happen, but there are still a few sponsor packages left.
Are you interested in coming? Buy your ticket now ;).
This year, I want to do more blogging again.
I have migrated my two blogs (this one and devteams.at) to a new technology technology, and I have completely re-designed them. And now I want to come back to at least a weekly blogging schedule.
And, almost most importantly: A really big contract will end this summer (because of a customer policy). So, I had to think about how to proceed professionally. But first, I probably will take a lot of time off in August in September, to spend with my family, go to conferences and to play with new technology.
Then, in mid-September or so, I want to start doing client work again. But this time, I would prefer more, smaller assignments instead of a single big one.
I think I can provide a lot of value for teams as an agile coach or a technical coach, even if I am there only for a few days per week - or even per month. We can pair-program or mob-program. By doing that, we will practice pair programming, mob programming, object oriented design, continuous refactoring, test driven development and more.
Or, I can help teams communicate better, find and solve problems and anti-patterns, facilitate meetings, …
Is this something that might be interesting? Autumn will be there sooner than you think, so let’s start to talk now.
I have decided to collect some tips, tricks and things I have learned about working with React, Redux, immutable.js and Flow.
Those are very interesting technologies, and they work together really well. But there are some caveats, some things that we learned the hard way.
Here is what I have learned in the last 2+ years of working with those technologies:
I just found this interesting book I wanted to share with you…
The Art of Service’s Software Testing Standard Requirements Excel Dashboard and accompanying eBook is for managers, advisors, consultants, specialists, professionals and anyone interested in Software Testing assessment.
Software quality is a topic that is very important for me right now, and it should be important for every team. This book contains a self-assessment that can be useful for your organization to find out where you currently are. If you struggle to ask the right questions about your testing efforts, this book will give many of them, and a way to score your answers.
Do you know the Single Responsibility Principle (SRP)?
The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.
It seems to generate a lot of confusion. Just a few days ago, Jon Reid had to clarify a misconception about that very principle:
SRP is misunderstood. Despite the name, it's not about some Platonic "single responsibility". No, it's "one reason to change." https://t.co/2iuCZH7oLd— Jon Reid (@qcoding) 29. Juni 2017
There, he refers to the definition of Robert C. Martin, who expresses the principle as, “A class should have only one reason to change.” In this post, I want to write down my own thoughts about the “Single Repsonsibilty Principle”. I hope I can clarify a few things, and do not add too much new confusion ;)
A Definition that "Works for Me"
So, some of the confusion comes from “single responsibility” vs. “single reason to change”. The definition that talks about “responsibilities” is hard to follow in practice: What exactly is a responsibility? What if I can divide the responsibility in multiple sub-responsibilities? How can I make sure that all the code is part of the same responsibility?
On the other hand, “one reason to change” sounds more like a heuristic to me than a real definition. Yes, when a class has many different responsibilities, it also has many reasons to change. But if that was the definition, we should rename the principle to “SRTCP” (Single Reason To Change Principle).
So, I was searching for a definition that works for me and that gives me some guidance in my day-to-day work. After discussing with several very good developers, I nowadays really like the following definition:
You can describe everything a design element (method, class, module, ...) does - at a reasonable level of abstraction - as a single, coherent thing.
In other words, if you use “CRC cards” to desing your classes, the “Responsibilities” column should contain a single bullet point.
If the level of abstraction is too high, you can describe everything as a single thing (“Is a part of the XYZ system”). If the level of abstraction is too low, everything has many responsibilities (“Execute assembler instruction x. Execute assembler instruction y. …”). So what is a “reasonable” level of abstraction?
We’ll come to that soon, after an example…
When I teach TDD workshops, I ask the attendees to implement Hangman (the word guessing game) - even multiple times. I now want to discuss with you a few possible designs for implementing this game (none of them is the best possible design, of course).
Let’s start simple. The whole problem is so easy, you can implement everything in a single class:
This design has a few disadvantages. Most glaringly, in a TDD class: It is really hard to test. Especially if you do not make any compromises like making methods public that should actually be private.
We can try to simply split the class along the four responsibilities that we have already identified:
Now you can easily test three of the four classes, and with some work, you can probably even test the UI. And you can test every class in complete isolation from the other classes, which is great for achieving stable tests…
Is 4 classes for such a simple problem over-engineering? Quit possibly. But I am trying to make a point here…
(Single) Level of Abstraction
So, how do you decide which level of abstraction is right?
There are already several levels in your application, e.g.:
program / library > package / module > public classes > public methods > private methods > package protected classes > public methods > private methods
To add clarity to your design, make sure that all design elements in a level are roughly on the same level of abstraction (yes, there is gut-feeling involved in deciding that).
So, all the public methods in a class should be roughly on the same level of abstraction, and the class itself would be on a higher level. They delegate to private methods of that class to do the real work, which are on a lower level.
Sometimes you can find interesting responsibilities by looking at the tests of a class or method. And when you split it, you might need new design elements (a new package or a new class) to keep everything at roughly the same level of abstraction.
Tests and Responsibilities
Hangman - Implements the flow of a single Hangman game, given a secret word. √ returns a hint that contains only underscores at the start of the game √ shows a hint with the correct length for the secret word "test" at the start of the game √ shows a hint with the correct length for the secret word "a" at the start of the game √ shows a hint with the correct length for the secret word "few" at the start of the game √ shows a hint with the correct length for the secret word "cases" at the start of the game √ updates hint to "c____" after guessing "c" when word is "cases" √ updates hint to "c_s_s" after guessing "c,s" when word is "cases" √ updates hint to "c_ses" after guessing "c,s,e" when word is "cases" √ does not update the hint when making a wrong guess √ decrements the number of remaining tries after a wrong guess √ does not decrement the number of wrong guesses after a right guess √ indicates game is over ("Lost") when there was only one guess remaining and the user guessed wrong √ indicates game is over ("Won") when the user guessed all letters of the secret word √ does not accept any input after the game is over
Oh, some of these tests seem to belong together. Let’s group them, and look at the test output again:
Hangman - Implements the flow of a single Hangman game, given a secret word. Generates Hints from the secret word and the input √ returns a hint that contains only underscores at the start of the game √ shows a hint with the correct length for the secret word "test" at the start of the game √ shows a hint with the correct length for the secret word "a" at the start of the game √ shows a hint with the correct length for the secret word "few" at the start of the game √ shows a hint with the correct length for the secret word "cases" at the start of the game √ updates hint to "c____" after guessing "c" when word is "cases" √ updates hint to "c_s_s" after guessing "c,s" when word is "cases" √ updates hint to "c_ses" after guessing "c,s,e" when word is "cases" √ does not update the hint when making a wrong guess Keeps track of remaining guesses, so UI can draw the gallows pole √ decrements the number of remaining tries after a wrong guess √ does not decrement the number of wrong guesses after a right guess Keeps track of whether the game is running or over (Won / Lost) √ indicates game is over ("Lost") when there was only one guess remaining and the user guessed wrong √ indicates game is over ("Won") when the user guessed all letters of the secret word √ does not accept any input after the game is over
It seems like this class has three different responsibilities (at least at some level of abstraction). So, if I wanted, I could split this “Rules” class even further, into one class for each of the groups, and one to coordinate them. Then I would probably need a package to group these two “Rules” classes, and the responsibility of that package could now be “Implements the state changes of a single game, based on the rules”.
Does it always make sense to split a class like that? That depends on a lot of things, but from the perspective of the Single Responsibility Principle, we could do it…
The Single Responsibility Principle gives you an indicator when to change your design. Split your methods / classes / modules when they have more than one responsibility. Restructure code when your classes / methods / modules do not fully encapsulate that responsibility.
When your design elements have many different responsibilities, they have many reasons to change. And they are also hard to test. When your design elements do not reasonably encapsulate their responsibility, changes will cascade through your code. And again, it will be harder to test.
But do not start to split all your classes alon their reponsibilities right away! The SRP should not be the only driving force of your designs - There are other forces, and sometimes they give you conflicting advice. Take, for example, the SOLID principles - 5 design principles, where the SRP is only one of them.
By the way: Jon Reid has now also written an article about the SRP: Single Responsibility Principle: Is It a Fundamental Mistake? Go read it, it’s excellent!
Being a freelance consultant / coach, I have worked with many different teams in the last 10+ years. As far as I am concerned, I never was the best developer on the team.
No, I do not have any proof for that, of course not. It is more of a mind set than something that can be objectively proven to be true or false. Let me explain…
Learning Every Day
Learn from everyone, follow noone.
If you hire me, I come to your company to learn something. Yes, I also come to provide more value for you than you pay for me - To teach you something, to coach your team, to help you solve a problem, to write some code. That’s why you hired me.
But for me, it is also a learning experience. I am trying to get better at what I do every day. I read many books, blogs, and I try to learn from everyone. And I really think that you can learn something from everyone: Even the most senior developer might learn a thing or two from the most junior, if she is open to learning something.
Now, if I would be in a team and have an attitude like “I am the best X here”, learning from everyone else just becomes a lot harder, at least in the field of X. Also, I think others on the team might notice that attitude, and this would also hinder mutual learning / teaching.
But What if I am the Best?
Let’s assume, hypothetically, that I come to a team, where after some time, all my evidence suggests that I am really the best developer (I do not think this has ever happened to me). I would still try hard to think that I am not the best.
A little humility can help in many aspects. Mutual learning / teaching, like above, is one of them. A better team culture is another. When you ask for help, you encourage people to talk to you on the same level.
Too Many Dimensions
Also, there are too many dimensions where you can be good in software development. And a team working on any non-trivial task needs many of those dimensions.
So who is the best developer - The person who is good at keeping the overall architecture in mind, the person who knows all the details of your programming language, the person who is getting the details right, or anyone else on the team? You need all of them!
I think that I, personally, are quite competent in several programming languages, in software architecture / design, in techniques for better software quality, in facilitating better team practices. I think that, over time, I have become quite good at dealing with legacy code. I am also quite competent as a trainer and as a technical coach.
But I am probably not the best developer or tester or architect or coach or trainer you have ever seen.
Good in Multiple Disciplines
I think Scott Adams (I cannot find the quote anymore) once wrote that it is really hard to become world class in a single field, but when you become merely quite good in multiple fields, you might already be world class for this combination of fields. And this can be very valuable.
I always liked this idea, and I try to work like that. This is why I think I am competent in so many different fields, but surely not the best in any of them. And so far, this has worked quite well for me.
But even this is not what I am trying to say.
My goal is to keep learning, to keep becoming better. And a little humility can help a lot here.
When my state of mind is “I am not the best X in here”, the questions become “What can I learn from you?” or “How can you help me today?” and, of course, “How can we help each other today?”.
But, of course, this is also something I have to constantly work on. I always have to remind myself to ask these questions. So this post is also a little reminder to myself ;)
Last week, I hosted an online training course for people from all over Europe. There they learned how to build web applications with React and Redux. We recorded Videos during the talk, and I prepared some more Videos and Training Materials for you. Get them here:
I will be teaching a free, online React / Redux Workshop for up to 8 students.
Date: Thu March 9th - Fri March 10th, 2017, 9:00 - 17:00 Location: online (probably Hangouts)
Here’s how it will work:
- Register with the form below.
- 8 people who registered will be in the live workshop, everyone else will be on the waiting list.
- I will record the workshop. By registering, you agree that I can record the workshop and publish the recordings online, either for free or for profit.
- If everything works (quality of the recording, ...), the 8 participants and everyone on the waiting list will get the videos for free.
- If you don't have time for the live sessions, you can still register. Answer the "Video"-question with "No", you will still get the free videos afterwards.
Hope to see you there! Register here (Mobile users: Swipe up/down to navigate the questions):
Registration is closed now. Follow @dtanzer to get updates.
Any questions? Business@DavidTanzer.net
Of course there are defects in legacy code. But when you are a developer working on changing, refactoring or enhancing legacy code, many of the “defects” you’ll find are probably desired behaviour (or have been, at some point in time). And even when they are not, you often still cannot be sure if you can fix the undesired behaviour without negatively affecting users. Let me explain…
...Changes Colour Unexpectedly
I once wrote some piece of really bad code that I use when I facilitate refactoring exercises at conferences, at user group meetups and during my trainings: The BabySteps Timer. A few days ago, I invited people on Twitter to refactor this code, as a challenge.
One of the developers who accepted the challenge, Franziska, submitted a defect report:
Clock resets to white colour when time is over Not sure if bug or feature, if the time has expired, the clock turns red, starts counting down from 2:00 again and then it turns white after a couple of seconds (see screenshots). Issue #1 by Franziska Sauerwein
I am fully aware that Franziska only reported some unexpected behaviour she saw, and did not have any intent of changing that behaviour during a refactoring exercise.
But this reminded me of a situation I experienced at a past client, where we actually changed some unexpected behaviour, and it didn’t turn out well…
But... The Numbers are Wrong!
Some years ago, I was working with 2 Scrum teams as an agile coach. When I joined, they were already one year into a project where they were changing some legacy code: They refactored the server, and they completely re-wrote the client.
In addition to its main purpose, the software calculated some statistics. When we started to work on that feature, we found out that the numbers it calculated were wrong. Under some conditions, it just gave inaccurate answeres, that were at least in the right ballpark, but sometimes the numbers were not only inaccurate - just plain wrong.
None of the business analysts could remember what this statistics feature was used for, so we tried to find out who uses it. We looked at the logs of the last 6 months and found out: It was never used! Not a single time!
Our Product Owner asked some key stakeholders, but noone could explain to us why this feature would be required. So we removed it.
A few months later, the “Blocker” defects were rolling in: “The statistics feature is missing! We cannot do our work!” they said.
We found out that this feature was only used during 4-6 weeks of the year and only by some users, and we did not look at this time frame when searching the logs.
We tried to convince the users to allow us to change the statistics so they’ll get the correct numbers. But we did not get a budget for that. They just wanted the old feature back, because “We only use them as ballpark estimates anyway”. “And when the software gives you wrong numbers?” we asked - “Oh, we can spot that”, they said.
So we put the faulty feature back in. But even after we put the feature back in, nobody wanted us to fix the defects…
Unexpected != Defect
In a real legacy code situation, don’t expect that you have found a defect when you have found some really strange behaviour. Ask business analysts. Ask stakeholders. Ask real users. Gather data from usage statistics and logs.
And even after you did all this, you cannot be 100% sure…
Yes, there are defects in legacy code. And yes, you will find some of them. But, as a developer, beware of just fixing the defect (especially if you are new to the project): The strange behaviour might not be a defect at all. And even if it is one, your fix might cause more problems than the defect.
Update: This post was on the front page of hacker news. Read the discussion here.
Side Note: I also changed the README of the babysteps timer to make it more clear that it is mainly an exercise/kata, not a program you should use. Even though you can use it, if you want.
Read more about how software architecture and design impact your agility in my book “Quick Glance At: Agile Anti-Patterns”: Buy it now!
Yesterday I facilitated a “legacy code refactoring” session at the Softwerkskammer München Meetup. There were ~50 craftswoman and craftsmen, and all of them were coding, trying to improve some particularily bad code I wrote.
We did three different exercises, each of them for 30 minutes, and in all of them, we tried to bring a piece of bad code under test.
First I gave them a very short (actually too short - but that was on purpose) introduction to the code: The “Babysteps Timer”. This is a very simple GUI program I wrote some time ago, because I needed a short example of really bad code that I could use for refactoring exercises.
Clone it on GitHub if you want to try the exercise yourself: https://github.com/dtanzer/babystepstimer.
The application is just a timer that counts down from 02:00 to zero. Ten seconds before reaching zero, it plays a sound. When it actually reaches zero, it plays a sound and changes the background color to red for a few seconds. When you reset it before it reaches zero, it changes the background color to green for a few seconds. That’s it.
The code is reasonably short (a single class with ~150 lines without the Java boilerplate) and most variables are reasonably well named (for some definition of “reasonably” ;) ). Still it is very hard to change: The class has more than 10 different responsibilities, there are two threads, lots of inner classes, and every small part of the code is coupled to almost everything else.
The code makes testing particularily hard because it makes several calls to
System.currentTimeMillis() and state changes take a long time: After starting the timer, you need to wait a full second (that’s 1 000 000 microseconds!) until you see the timer changing. So when your tests can control how fast time progresses for the application, you can test the application more easily and the tests will run faster.
Exercise 1: Refactor, then test
Sometimes, it is so hard to find seams for testing in a bit of code that it makes sense to first refactor a bit to make the code more testable, and then add some tests. This was our first exercise:
Perform some refactoring to make the code testable, then write a test.
Participants could do any refactorings they wanted - Get rid of all the static stuff, extract inner classes to their own files, extract some methods, rename stuff - Whatever. But their goal should be to write a first test (any kind of test - unit test or integrated test or behavior test) after ~25 minutes of refactoring.
I told them to rely on their IDEs as much as possible to minimize the amount of manual testing, which slows you down considerably because a full timer cycle lasts 2 minutes.
As far as I know, nobody got to the point where they could write a first green test. And that was exactly the point of this exercise: I wanted to show them how hard it is to first refactor, then test. Even when it’s often tempting. Especially in a code base like this, with so much coupling and so little cohesion.
Exercise 2: Test, then refactor
OK, so if we cannot easily make this code testable, maybe we can find a way to test it without changing it. At first, this code looks like there are no seams for testing, but there actually is one: The
JTextPane that contains the whole user interface. You can get the whole HTML from this text pane, and you can invoke it’s
HyperlinkListener to simulate button clicks. So, our second exercise was:
Write some high-level functional tests using the timerPane’s HTML and hyperlink listener. Then start to refactor
I also told them that, once they have a few tests, a good first refactoring would be to try to control the progress of time, because this would speed up their existing tests.
Writing those tests will not make the hard refactorings from Exercise 1 any easier, but at least you’ll have a safety net of tests before you do it. When you do it right, you can write automated test for the whole user-visible functionality without making any big changes, and then speed them up by controlling time. Then you can start the refactoring with fast, automated tests as your safety net.
I don’t think any pair actually did a bigger refactoring. Bot several people told me afterwards that it was an eye-opener for them how easy it was to write the tests before the refactoring, compared to refactoring first.
Exercise 3: Golden Master
I also showed them the Golden Master techniqe and told them that it is sometimes very well suited for testing legacy code. So this was our third exercise:
Add log statements to the code, save the log output as your golden master, and compare future runs to this golden master.
I added Samir Talwar’s Smoke test framework and told them they could use it if they want. Or they could also just save the logs to files and compare them with whatever tool they had. Not everyone could use Smoke because some didn’t have Ruby installed…
Even though the “Baby Steps Timer” is not very well suited for “Golden Master” testing, people were very interested in this exercise. Most didn’t know “Golden Master” before, and some said they wanted to try it on some “real” code.
Fun and Learning
I had a lot of fun yesterday, and I learned a lot. I hope most or all attendees feel the same :) So thanks to everyone at Softwerkskammer München and LV 1871 who made this event possible.
If you want to run a legacy code refactoring session at your meetup or in your company, feel free to use the Baby Steps Timer. If you want some tips for facilitating the session, or if you have some ideas/improvements for me, just contact me - I’ll be happy to help and/or listen. And if you want me to facilitate that session or run a longer training for you, I can do that too: Let’s talk ;)
Last week, I first heared about React Native Ubuntu. I thought: That’s awesome - Now I can develop desktop applications for all major operating systems (since there is also a React Native Windows and React Native MacOS). So I had to give it a try.
The only problem: My main laptop is running Fedora, and I do not want to switch to Ubuntu just for toying with a new technology. Well, I tried anyway, and I was pleasantly surprised that it was not very hard to set up. (Wouldn’t it be really cool if Canonical and RedHat would work together on react native and renamed it to “React Native Linux”? Tell them!).
So, here’s what I did to install and run the react native starter app on my Fedora system:
Clone the GitHub Repo
I will clone the react-native-ubuntu repository to a directory where I have my development libraries (“
devel-libs” in this case):
[devel-libs]$ git clone https://github.com/CanonicalLtd/react-native -b ubuntu
And then you need to install sinopia (a local npm registry), uninstall the react-native command line interface, reinstall it from the ubuntu branch, change the registry for the globally installed npm to sinopia, …
Installing Stuff Globally?
So, the developers really want me to modify the global environment on my laptop? I won’t do that. Especially not for playing around with a technology that I might not even need.
The thing is: If your library requires me to install anything globally on my development machine, you have a coupling problem. Which is an architectural problem. Go, fix it.
But: I wanted to try react-native-ubuntu anyway. I thought about using a Vagrant VM, but I first wanted to try to install everything locally - within my example project. And it worked! So throw away the
react-native-cli/README.md and bear with me…
Install Qt Dependencies
I actually did install some dependencies globally - The Qt 5 libraries that are needed to compile and run the react-native app:
$ sudo dnf install Qt5Core $ sudo dnf install qt5-default $ sudo dnf install qt5-qtbase-devel
I think those were all. You might need different libraries, depending on your system setup…
Well, let’s just try to install react-native locally, in a directory where I want to create my example application (“
path/to/react-native” is the directory where I’ve checked out the ubuntu branch of react-native before.
[example]$ npm install ~/path/to/react-native [example]$ npm install ~/path/to/react-native/react-native-cli/
Now I can run the react-native-cli from the local installation in “
[example]$ node node_modules/react-native-cli/index.js init TestApp Looks like React Native project already exists in the current folder. Run this command from a different folder or remove node_modules/react-native
OK, react-native-cli wants to download react-native. That’s why the official guide wants me to install sinopia (which I still want to avoid). Maybe there’s a workaround? Let’s delete the react-native module, let the cli do whatever it wants to do, and then install react-native from the ubuntu branch again…
[example]$ rm -rf node_modules/react-native [example]$ node node_modules/react-native-cli/index.js init TestApp [example]$ cd TestApp/ [TestApp]$ rm -rf node_modules/react-native/ [TestApp]$ npm install ~/Documents/Development/react-native [TestApp]$ node ../node_modules/react-native-cli/index.js run-ubuntu
And now our react-native app is running! Isn’t it awesome?
Now I can start playing with developing desktop apps using react-native. Maybe I’ll try react-native-windows next…