Help Me to Improve My Writing Style

Some time I read a blog titled: Finding your voice when writing a technical book. It reminded me that I have been writing this blog for four and half years, but I haven’t paid a lot of attention to my "voice". That is going to change right now.

If I think about my past blog posts, I can identify two different voices that I have been using during four and half years.

The truth is that I am not happy with either one of them.

The biggest problem of these two voices is that my first voice described nothing and my current voice describes everything.

Let’s move on and take closer look at my old voice.

Voice 1: Describe Nothing

Let’s demonstrate my first voice by using an example. The first part of my Spring Data JPA tutorial is a perfect example of my first voice.

When I describe how the reader can configure the Spring application context, I use the following text and sample code:

Second, you must configure the Spring application context. As you might remember, you need to configure the data source, transaction manager and entity manager factory beans. If you are using Spring 3.1 and Servlet 3.0, you can do this by implementing a Java configuration class and loading that configuration class in your web application initializer. The content of my application context configuration class is given in following:

import com.jolbox.bonecp.BoneCPDataSource;
import org.hibernate.ejb.HibernatePersistence;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.*;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
 
import javax.annotation.Resource;
import javax.sql.DataSource;
 
/**
 * An application context Java configuration class. The usage of Java configuration
 * requires Spring Framework 3.0 or higher with following exceptions:
 * <ul>
 *     <li>@EnableWebMvc annotation requires Spring Framework 3.1</li>
 * </ul>
 * @author Petri Kainulainen
 */
@Configuration
@ComponentScan(basePackages = {"net.petrikainulainen.spring.datajpa.controller"})
@EnableWebMvc
@ImportResource("classpath:applicationContext.xml")
@PropertySource("classpath:application.properties")
public class ApplicationContext {
     
    private static final String VIEW_RESOLVER_PREFIX = "/WEB-INF/jsp/";
    private static final String VIEW_RESOLVER_SUFFIX = ".jsp";
 
    private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
    private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
    private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
    private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
 
    private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
    private static final String PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";
    private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
 
    private static final String PROPERTY_NAME_MESSAGESOURCE_BASENAME = "message.source.basename";
    private static final String PROPERTY_NAME_MESSAGESOURCE_USE_CODE_AS_DEFAULT_MESSAGE = "message.source.use.code.as.default.message";
 
    @Resource
    private Environment environment;
 
    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();
 
        dataSource.setDriverClass(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
        dataSource.setJdbcUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
        dataSource.setUsername(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
        dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
 
        return dataSource;
    }
 
    @Bean
    public JpaTransactionManager transactionManager() throws ClassNotFoundException {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
 
        transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
 
        return transactionManager;
    }
 
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() throws ClassNotFoundException {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
 
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan(environment.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
 
        Properties jpaProterties = new Properties();
        jpaProterties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
        jpaProterties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
        jpaProterties.put(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY));
        jpaProterties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
 
        entityManagerFactoryBean.setJpaProperties(jpaProterties);
 
        return entityManagerFactoryBean;
    }
 
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
 
        messageSource.setBasename(environment.getRequiredProperty(PROPERTY_NAME_MESSAGESOURCE_BASENAME));
        messageSource.setUseCodeAsDefaultMessage(Boolean.parseBoolean(environment.getRequiredProperty(PROPERTY_NAME_MESSAGESOURCE_USE_CODE_AS_DEFAULT_MESSAGE)));
 
        return messageSource;
    }
 
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
 
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix(VIEW_RESOLVER_PREFIX);
        viewResolver.setSuffix(VIEW_RESOLVER_SUFFIX);
 
        return viewResolver;
    }
}

This voice has one major problem:

If the reader doesn’t already know how she can configure the Spring application context by using the Java configuration, it is very hard to understand what is going on. In other words, this style assumes that the reader already knows what she has to do.

After I realized this problem, I decided to change my writing style.

Voice 2: Describe Everything

The problem of my first voice was that I didn’t describe the code at all. I decided fix that by describing the implementation of the sample code before showing any code to the reader.

My latest blog post titled: Using Asciidoctor with Spring: Rendering Asciidoc Document with Spring MVC has a good example of my second voice.

When I describe how the reader can create an abstract view class that transforms Asciidoc documents into HTML and renders the created HTML, I use the following text and code:

First, we have to implement the AbstractAsciidoctorHtmlView class. This class is an abstract base class that transforms Asciidoc markup into HTML and renders the created HTML. We can implement this class by following these steps:

  1. Create the AbstractAsciidoctorHtmlView class and extend the AbstractView class.
  2. Add a constructor to the created class and set the content type of the view to 'text/html'.
  3. Add a protected abstract method getAsciidocMarkupReader() to created class and set its return type to Reader. This method must be implemented by the subclasses of this class and the implementation of this method must return a Reader object that can be used to read the rendered Asciidoc markup.
  4. Add a private getAsciidoctorOptions() method to the created class. This method returns the configuration options of Asciidoctor. Implement it by following these steps:
    1. Create a new Options object.
    2. Ensure that Asciidoctor renders both the header and footer of the Asciidoctor document when it is transformed into HTML.
    3. Return the created Options object.
  5. Override the renderMergedOutputModel() method of the AbstractView class and implement it by following these steps:
    1. Get the content type of the view by calling the getContentType() method of the AbstractView class and use the returned object when you set the content type of the response. This sets the content type of the response to 'text/html'.
    2. Create a new Asciidoctor object by invoking the create() method of the Asciidoctor.Factory class.
    3. Get an Options object by invoking the private getAsciidoctorOptions() method.
    4. Get the Reader object that is used to read the Asciidoctor markup by invoking the getAsciidocMarkupReader() method.
    5. Get the Writer object that is used to write created HTML markup to the response body by invoking the getWriter() method of the ServletResponse interface.
    6. Transform the Asciidoc markup into HTML and write the created HTML to the response body by invoking the render() method of the Asciidoctor class. Pass the Reader, Writer, and Options objects as method parameters.

The source code of the AbstractAsciidoctorHtmlView class looks as follows:

import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Options;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.view.AbstractView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Reader;
import java.io.Writer;
import java.util.Map;

public abstract class AbstractAsciidoctorHtmlView extends AbstractView {

    public AbstractAsciidoctorHtmlView() {
        super.setContentType(MediaType.TEXT_HTML_VALUE);
    }

    protected abstract Reader getAsciidocMarkupReader();

    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws Exception {
        response.setContentType(super.getContentType());

        Asciidoctor asciidoctor = Asciidoctor.Factory.create();
        Options asciidoctorOptions = getAsciidoctorOptions();

        try (
                Reader asciidoctorMarkupReader = getAsciidocMarkupReader();
                Writer responseWriter = response.getWriter();
        ) {
            asciidoctor.render(asciidoctorMarkupReader, responseWriter, asciidoctorOptions);
        }
    }

    private Options getAsciidoctorOptions() {
        Options asciiDoctorOptions = new Options();
        asciiDoctorOptions.setHeaderFooter(true);
        return asciiDoctorOptions;
    }
}

Even though I think that my second voice is a lot better than the first one, I still feel that it has two serious problems:

  • It makes my blog posts hard to read because I describe every implementation detail (even trivial ones) before the reader can see any code.
  • It makes my blog posts longer than they should be.

It is time to fix these problems, and I hope that you can help me to do it.

Voice 3: Finding the Middle Ground

I think that my third voice should be a compromise between my first and second voice. I think if I want make that compromise, I should follow these rules:

  • Describe what the code does and why it does it.
  • Add all other comments to the source code.
  • Add other links to an "additional information" box found below the source code.

If I follow these steps, the text that describes how the reader can create an abstract view class that transforms Asciidoc documents into HTML and renders the created HTML would look as follows:

First, we have to implement the AbstractAsciidoctorHtmlView class. This class is an abstract base class that transforms Asciidoc markup into HTML and renders the created HTML. We can implement this class by following these steps:

  1. Create the AbstractAsciidoctorHtmlView class and extend the AbstractView class.
  2. Add a constructor to the created class and set the content type of the view to 'text/html'.
  3. Add a protected abstract method getAsciidocMarkupReader() to created class and set its return type to Reader. This method must be implemented by the subclasses of this class and the implementation of this method must return a Reader object that can be used to read the rendered Asciidoc markup.
  4. Add a private getAsciidoctorOptions() method to the created class and implement it by returning the configuration options of Asciidoctor.
  5. Override the renderMergedOutputModel() method of the AbstractView class, and implement it by transforming Asciidoc document into HTML and rendering the created HTML.

The source code of the AbstractAsciidoctorHtmlView class would look as follows:

import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Options;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.view.AbstractView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Reader;
import java.io.Writer;
import java.util.Map;

public abstract class AbstractAsciidoctorHtmlView extends AbstractView {

    public AbstractAsciidoctorHtmlView() {
        super.setContentType(MediaType.TEXT_HTML_VALUE);
    }

    protected abstract Reader getAsciidocMarkupReader();

    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws Exception {
        //Set the content type of the response to 'text/html'
        response.setContentType(super.getContentType());

        Asciidoctor asciidoctor = Asciidoctor.Factory.create();
        Options asciidoctorOptions = getAsciidoctorOptions();

        try (
                //Get the reader that reads the rendered Asciidoc document
                //and the writer that writes the HTML markup to the request body
                Reader asciidoctorMarkupReader = getAsciidocMarkupReader();
                Writer responseWriter = response.getWriter();
        ) {
            //Transform Asciidoc markup into HTML and write the created HTML 
            //to the response body
            asciidoctor.render(asciidoctorMarkupReader, responseWriter, asciidoctorOptions);
        }
    }

    private Options getAsciidoctorOptions() {
        Options asciiDoctorOptions = new Options();
        //Ensure that Asciidoctor renders both the header and footer of the Asciidoctor 
        //document when it is transformed into HTML.
        asciiDoctorOptions.setHeaderFooter(true);
        return asciiDoctorOptions;
    }
}
Additional Reading:

I could add links to API docs, reference manuals or other blog posts after the source code.

I think that this writing style would be a big improvement because:

  • My blog post posts would be shorter and easier to read because I wouldn’t explain every implementation detail before the reader will see any code.
  • The reader would still get the information she needs to understand my code. The only difference is that she will get the information at the “right time”.

What Do You Think?

Now is your chance to give me feedback about my writing style. I am interested in hearing the answers to the following questions:

  • Do you think that my current writing style is too verbose?
  • Do you think that the voice three would be an improvement?
  • Do you have any other feedback for me?

Thank you in advance!

11 comments… add one
  • Vlad Mihalcea Sep 28, 2014 @ 19:39

    I usually don't describe basic stuff assuming the reader will read the Spring manual for basic configuration.

    Describing Sitemesh and the Spring web config might be two separate posts, while the actual AsciiDoc controller a third one referencing the first two.

    I like to divide and conquer as much as possible and more posts is not a bad thing either. It keeps the audience entertained.

    • Petri Sep 28, 2014 @ 20:44

      Hi Vlad,

      Thank you for your comment. I really appreciate your help!

      It seems that I have to pay more attention to planning the content of my blog posts. I am using Trello for this, but it seems that I am not doing a very good job. I will start doing this right away.

  • Janne Sinivirta Sep 28, 2014 @ 20:17

    I would post just super small snippets code, just the very interesting few lines. The rest could be pushed as a working example on GitHub. Working examples beat many pages of code on a blog page every time. So focus on just a single or few interesting nuggets.

    For a bigger topic, start from a really small example in one blog post and then keep building on top of it in later posts and you can always have the code available in repo.

    I have to be a jerk and point that at least this blog post would have benefited from proof-reading. Lots of "the"'s and "a"'s etc. missing that distract from the digesting of the actual info.

    • Petri Sep 28, 2014 @ 20:56

      Hi Janne,

      Thank you for your comment! Your comment is very useful to me because you weren't afraid of giving negative feedback.

      would post just super small snippets code, just the very interesting few lines. The rest could be pushed as a working example on GitHub. Working examples beat many pages of code on a blog page every time. So focus on just a single or few interesting nuggets.

      For a bigger topic, start from a really small example in one blog post and then keep building on top of it in later posts and you can always have the code available in repo.

      This is a good advice. It seems that I will have to plan my blog posts before I write them. Also, I guess that I have to learn to identify the essential (and interesting) stuff and drop the trivial stuff out of the blog post. Like you said, the readers can always check it out from the Github repository.

      I have to be a jerk and point that at least this blog post would have benefited from proof-reading. Lots of “the”‘s and “a”‘s etc. missing that distract from the digesting of the actual info.

      Thank you for being a jerk! I really like that people point out my mistakes because otherwise I cannot correct them. I will have to pay more attention to proofreading. Unfortunately I am not a grammar jedi yet, but I hope that I fix at the least some of the mistakes you mentioned.

  • Alexandre Kieling Oct 2, 2014 @ 22:38

    I agree with Janne that code snippets should be as small as possible. I also think they should be used only when the information cannot be better expressed in words. I read lots of technical articles and usually read them until the end, but sometimes lose interest when there are long pieces of code.
    About the level of details when writing step-by-step guides to do something, I suggest learning from well written documentation we find on the Internet. For example, I find Github's documentation about the generation of SSH keys really good (https://help.github.com/articles/generating-ssh-keys/). See how they use steps and small code snippets to guide the reader through the task.

    • Petri Oct 3, 2014 @ 19:45

      Hi Alexandre,

      Thank you for your comment!

      It is really valuable to me because it made me realize that long pieces of code can be a problem too. Also, thank you for linking to the Github's documentation. It really helped me to understand your point.

  • Greg Turnquist Oct 3, 2014 @ 22:05

    I'm glad to have inspired you to reexamine things!

    Another factor that is different is when you're writing a book vs. writing a blog. With blogs, you can write smaller stuff and then write sequels to explore other areas. You have the benefit of expounding upon things later on. With books, you have to think about the entire product you will deliver and the fact that when its published, there are no more additions to be made. That may drive you to write out more code than you would for a blog.

    With blogs, you don't have to explain as much because you also have the benefit of comments. Hence, books have to be a bit more comprehensive, but at some point, you have to make the judgment of how low level you really want to go, and whether that will help or harm your overall technical story.

    The biggest stuff I've discovered when learning about writing styles is to think about which books you enjoy the most. Why do you enjoy them? One of the best Isaac Asimov novels I read had these two rival characters throughout the book. They each had a different voice and I visualized them in completely different ways. And then I found out they were the same person on the last page of the novel. Asimov understood how to take full advantage of the medium he was using. In a similar sense, blogs and books are different mediums, and I enjoy people that put their personality into their writing.

    Good luck on this and future blogging and writing!

    • Petri Oct 4, 2014 @ 17:09

      Thank you for your inspiring comment!

      Your comment made me realize that I have been writing my blog posts (at least some of them) by using a style that is more suitable for writing a book. I think that I will have to modify my writing style a bit because I think that my current style might be a bit too "heavy" for the reader.

      The biggest stuff I’ve discovered when learning about writing styles is to think about which books you enjoy the most. Why do you enjoy them?

      This is a very good point. I have always liked the "In Action" series that is published by Manning. I will read a few of them and figure out why I like them so much.

      Again, thank you for writing such a valuable comment and good luck on your new book (Learning Spring Boot).

  • Mike Oct 15, 2014 @ 22:44

    What many tutorials lack is description of what happens under the hood of the code used.
    For example, I may know that I have to write certain code to establish DB connection, but I don't know what happens inside it, thus when I face an issue when connection can't be established I can't figure out why.
    It would be great if you provided such explanation for essential parts of the program, to understand what happens inside and why it is needed.
    Good luck, I like you blog. :)

Leave a Reply