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:
1import com.jolbox.bonecp.BoneCPDataSource;
2import org.hibernate.ejb.HibernatePersistence;
3import org.springframework.context.MessageSource;
4import org.springframework.context.annotation.*;
5import org.springframework.context.support.ResourceBundleMessageSource;
6import org.springframework.core.env.Environment;
7import org.springframework.orm.jpa.JpaTransactionManager;
8import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
9import org.springframework.web.servlet.ViewResolver;
10import org.springframework.web.servlet.config.annotation.EnableWebMvc;
11import org.springframework.web.servlet.view.InternalResourceViewResolver;
12import org.springframework.web.servlet.view.JstlView;
13
14import javax.annotation.Resource;
15import javax.sql.DataSource;
16
17/**
18 * An application context Java configuration class. The usage of Java configuration
19 * requires Spring Framework 3.0 or higher with following exceptions:
20 * <ul>
21 * <li>@EnableWebMvc annotation requires Spring Framework 3.1</li>
22 * </ul>
23 * @author Petri Kainulainen
24 */
25@Configuration
26@ComponentScan(basePackages = {"net.petrikainulainen.spring.datajpa.controller"})
27@EnableWebMvc
28@ImportResource("classpath:applicationContext.xml")
29@PropertySource("classpath:application.properties")
30public class ApplicationContext {
31
32 private static final String VIEW_RESOLVER_PREFIX = "/WEB-INF/jsp/";
33 private static final String VIEW_RESOLVER_SUFFIX = ".jsp";
34
35 private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
36 private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
37 private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
38 private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
39
40 private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
41 private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
42 private static final String PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";
43 private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
44 private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
45
46 private static final String PROPERTY_NAME_MESSAGESOURCE_BASENAME = "message.source.basename";
47 private static final String PROPERTY_NAME_MESSAGESOURCE_USE_CODE_AS_DEFAULT_MESSAGE = "message.source.use.code.as.default.message";
48
49 @Resource
50 private Environment environment;
51
52 @Bean
53 public DataSource dataSource() {
54 BoneCPDataSource dataSource = new BoneCPDataSource();
55
56 dataSource.setDriverClass(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
57 dataSource.setJdbcUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
58 dataSource.setUsername(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
59 dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
60
61 return dataSource;
62 }
63
64 @Bean
65 public JpaTransactionManager transactionManager() throws ClassNotFoundException {
66 JpaTransactionManager transactionManager = new JpaTransactionManager();
67
68 transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
69
70 return transactionManager;
71 }
72
73 @Bean
74 public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() throws ClassNotFoundException {
75 LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
76
77 entityManagerFactoryBean.setDataSource(dataSource());
78 entityManagerFactoryBean.setPackagesToScan(environment.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
79 entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
80
81 Properties jpaProterties = new Properties();
82 jpaProterties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
83 jpaProterties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
84 jpaProterties.put(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY));
85 jpaProterties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
86
87 entityManagerFactoryBean.setJpaProperties(jpaProterties);
88
89 return entityManagerFactoryBean;
90 }
91
92 @Bean
93 public MessageSource messageSource() {
94 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
95
96 messageSource.setBasename(environment.getRequiredProperty(PROPERTY_NAME_MESSAGESOURCE_BASENAME));
97 messageSource.setUseCodeAsDefaultMessage(Boolean.parseBoolean(environment.getRequiredProperty(PROPERTY_NAME_MESSAGESOURCE_USE_CODE_AS_DEFAULT_MESSAGE)));
98
99 return messageSource;
100 }
101
102 @Bean
103 public ViewResolver viewResolver() {
104 InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
105
106 viewResolver.setViewClass(JstlView.class);
107 viewResolver.setPrefix(VIEW_RESOLVER_PREFIX);
108 viewResolver.setSuffix(VIEW_RESOLVER_SUFFIX);
109
110 return viewResolver;
111 }
112}
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:
- Create the AbstractAsciidoctorHtmlView class and extend the AbstractView class.
- Add a constructor to the created class and set the content type of the view to ’text/html'.
- 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.
- 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.
- 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](http://docs.spring.io/autorepo/docs/spring-framework/4.0.5.RELEASE/javadoc-api/org/springframework/web/servlet/view/AbstractView.html#getContentType--) 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](http://docs.oracle.com/javaee/6/api/javax/servlet/ServletResponse.html#getWriter%28%29).
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:
1import org.asciidoctor.Asciidoctor;
2import org.asciidoctor.Options;
3import org.springframework.http.MediaType;
4import org.springframework.web.servlet.view.AbstractView;
5
6import javax.servlet.http.HttpServletRequest;
7import javax.servlet.http.HttpServletResponse;
8import java.io.Reader;
9import java.io.Writer;
10import java.util.Map;
11
12public abstract class AbstractAsciidoctorHtmlView extends AbstractView {
13
14 public AbstractAsciidoctorHtmlView() {
15 super.setContentType(MediaType.TEXT_HTML_VALUE);
16 }
17
18 protected abstract Reader getAsciidocMarkupReader();
19
20 @Override
21 protected void renderMergedOutputModel(Map<String, Object> model,
22 HttpServletRequest request,
23 HttpServletResponse response) throws Exception {
24 response.setContentType(super.getContentType());
25
26 Asciidoctor asciidoctor = Asciidoctor.Factory.create();
27 Options asciidoctorOptions = getAsciidoctorOptions();
28
29 try (
30 Reader asciidoctorMarkupReader = getAsciidocMarkupReader();
31 Writer responseWriter = response.getWriter();
32 ) {
33 asciidoctor.render(asciidoctorMarkupReader, responseWriter, asciidoctorOptions);
34 }
35 }
36
37 private Options getAsciidoctorOptions() {
38 Options asciiDoctorOptions = new Options();
39 asciiDoctorOptions.setHeaderFooter(true);
40 return asciiDoctorOptions;
41 }
42}
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:
- Create the AbstractAsciidoctorHtmlView class and extend the AbstractView class.
- Add a constructor to the created class and set the content type of the view to ’text/html'.
- 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.
- Add a private getAsciidoctorOptions() method to the created class and implement it by returning the configuration options of Asciidoctor.
- 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:
1import org.asciidoctor.Asciidoctor;
2import org.asciidoctor.Options;
3import org.springframework.http.MediaType;
4import org.springframework.web.servlet.view.AbstractView;
5
6import javax.servlet.http.HttpServletRequest;
7import javax.servlet.http.HttpServletResponse;
8import java.io.Reader;
9import java.io.Writer;
10import java.util.Map;
11
12public abstract class AbstractAsciidoctorHtmlView extends AbstractView {
13
14 public AbstractAsciidoctorHtmlView() {
15 super.setContentType(MediaType.TEXT_HTML_VALUE);
16 }
17
18 protected abstract Reader getAsciidocMarkupReader();
19
20 @Override
21 protected void renderMergedOutputModel(Map<String, Object> model,
22 HttpServletRequest request,
23 HttpServletResponse response) throws Exception {
24 //Set the content type of the response to 'text/html'
25 response.setContentType(super.getContentType());
26
27 Asciidoctor asciidoctor = Asciidoctor.Factory.create();
28 Options asciidoctorOptions = getAsciidoctorOptions();
29
30 try (
31 //Get the reader that reads the rendered Asciidoc document
32 //and the writer that writes the HTML markup to the request body
33 Reader asciidoctorMarkupReader = getAsciidocMarkupReader();
34 Writer responseWriter = response.getWriter();
35 ) {
36 //Transform Asciidoc markup into HTML and write the created HTML
37 //to the response body
38 asciidoctor.render(asciidoctorMarkupReader, responseWriter, asciidoctorOptions);
39 }
40 }
41
42 private Options getAsciidoctorOptions() {
43 Options asciiDoctorOptions = new Options();
44 //Ensure that Asciidoctor renders both the header and footer of the Asciidoctor
45 //document when it is transformed into HTML.
46 asciiDoctorOptions.setHeaderFooter(true);
47 return asciiDoctorOptions;
48 }
49}
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!